seeder-migration-issues

This commit is contained in:
RafficMohammed
2023-01-30 14:23:34 +05:30
parent 4d918c722f
commit 2ec836b447
3628 changed files with 116006 additions and 187 deletions

26
vendor/brozot/laravel-fcm/.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# General
.DS_Store
Thumbs.db
# Project
.env
composer.phar
vendor
build
node_modules
resources/assets/bower
Homestead.yaml
Homestead.json
storage/debugbar
doc/generated/*
generatedoc.sh
phpdoc.xml
build/*
# Editors
.sublime-workspace
.idea
.idea/*
.idea/workspace.xml
*.iml
_ide_helper.php

14
vendor/brozot/laravel-fcm/.travis.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
language: php
php:
- 5.6
before_script:
- composer self-update
- composer install --no-interaction
script:
- phpunit --coverage-clover build/logs/clover.xml
after_success:
- travis_retry php vendor/bin/coveralls

21
vendor/brozot/laravel-fcm/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Nicolas Brosy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

446
vendor/brozot/laravel-fcm/README.md vendored Normal file
View File

@@ -0,0 +1,446 @@
# Laravel-FCM
[![Build Status](https://travis-ci.org/brozot/Laravel-FCM.svg?branch=master)](https://travis-ci.org/brozot/Laravel-FCM) [![Coverage Status](https://coveralls.io/repos/github/brozot/Laravel-FCM/badge.svg?branch=master)](https://coveralls.io/github/brozot/Laravel-FCM?branch=master) [![Latest Stable Version](https://poser.pugx.org/brozot/laravel-fcm/v/stable)](https://packagist.org/packages/brozot/laravel-fcm) [![Total Downloads](https://poser.pugx.org/brozot/laravel-fcm/downloads)](https://packagist.org/packages/brozot/laravel-fcm)
[![License](https://poser.pugx.org/brozot/laravel-fcm/license)](https://packagist.org/packages/brozot/laravel-fcm)
## Introduction
Laravel-FCM is an easy to use package working with both Laravel and Lumen for sending push notification with [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/) (FCM).
It currently **only supports HTTP protocol** for :
- sending a downstream message to one or multiple devices
- managing groups and sending message to a group
- sending topics messages
> Note: The XMPP protocol is not currently supported.
## Installation
To get the latest version of Laravel-FCM on your project, require it from "composer":
$ composer require brozot/laravel-fcm
Or you can add it directly in your composer.json file:
```json
{
"require": {
"brozot/laravel-fcm": "1.3.*"
}
}
```
### Laravel
Register the provider directly in your app configuration file config/app.php `config/app.php`:
Laravel >= 5.5 provides package auto-discovery, thanks to rasmuscnielsen and luiztessadri who help to implement this feature in Laravel-FCM, the registration of the provider and the facades should not be necessary anymore.
```php
'providers' => [
// ...
LaravelFCM\FCMServiceProvider::class,
]
```
Add the facade aliases in the same file:
```php
'aliases' => [
...
'FCM' => LaravelFCM\Facades\FCM::class,
'FCMGroup' => LaravelFCM\Facades\FCMGroup::class, // Optional
]
```
> Note: The `FCMGroup` facade is needed only if you want to manage groups messages in your application.
Publish the package config file using the following command:
$ php artisan vendor:publish --provider="LaravelFCM\FCMServiceProvider"
### Lumen
Register the provider in your bootstrap app file ```boostrap/app.php```
Add the following line in the "Register Service Providers" section at the bottom of the file.
```php
$app->register(LaravelFCM\FCMServiceProvider::class);
```
For facades, add the following lines in the section "Create The Application" . FCMGroup facade is only necessary if you want to use groups message in your application.
```php
class_alias(\LaravelFCM\Facades\FCM::class, 'FCM');
class_alias(\LaravelFCM\Facades\FCMGroup::class, 'FCMGroup');
```
Copy the config file ```fcm.php``` manually from the directory ```/vendor/brozot/laravel-fcm/config``` to the directory ```/config ``` (you may need to create this directory).
### Package Configuration
In your `.env` file, add the server key and the secret key for the Firebase Cloud Messaging:
```php
FCM_SERVER_KEY=my_secret_server_key
FCM_SENDER_ID=my_secret_sender_id
```
To get these keys, you must create a new application on the [firebase cloud messaging console](https://console.firebase.google.com/).
After the creation of your application on Firebase, you can find keys in `project settings -> cloud messaging`.
## Basic Usage
Two types of messages can be sent using Laravel-FCM:
- Notification messages, sometimes thought of as "display messages"
- Data messages, which are handled by the client app
More information is available in the [official documentation](https://firebase.google.com/docs/cloud-messaging/concept-options).
### Downstream Messages
A downstream message is a notification message, a data message, or both, that you send to a target device or to multiple target devices using its registration_Ids.
The following use statements are required for the examples below:
```php
use LaravelFCM\Message\OptionsBuilder;
use LaravelFCM\Message\PayloadDataBuilder;
use LaravelFCM\Message\PayloadNotificationBuilder;
use FCM;
```
#### Sending a Downstream Message to a Device
```php
$optionBuilder = new OptionsBuilder();
$optionBuilder->setTimeToLive(60*20);
$notificationBuilder = new PayloadNotificationBuilder('my title');
$notificationBuilder->setBody('Hello world')
->setSound('default');
$dataBuilder = new PayloadDataBuilder();
$dataBuilder->addData(['a_data' => 'my_data']);
$option = $optionBuilder->build();
$notification = $notificationBuilder->build();
$data = $dataBuilder->build();
$token = "a_registration_from_your_database";
$downstreamResponse = FCM::sendTo($token, $option, $notification, $data);
$downstreamResponse->numberSuccess();
$downstreamResponse->numberFailure();
$downstreamResponse->numberModification();
// return Array - you must remove all this tokens in your database
$downstreamResponse->tokensToDelete();
// return Array (key : oldToken, value : new token - you must change the token in your database)
$downstreamResponse->tokensToModify();
// return Array - you should try to resend the message to the tokens in the array
$downstreamResponse->tokensToRetry();
// return Array (key:token, value:error) - in production you should remove from your database the tokens
$downstreamResponse->tokensWithError();
```
#### Sending a Downstream Message to Multiple Devices
```php
$optionBuilder = new OptionsBuilder();
$optionBuilder->setTimeToLive(60*20);
$notificationBuilder = new PayloadNotificationBuilder('my title');
$notificationBuilder->setBody('Hello world')
->setSound('default');
$dataBuilder = new PayloadDataBuilder();
$dataBuilder->addData(['a_data' => 'my_data']);
$option = $optionBuilder->build();
$notification = $notificationBuilder->build();
$data = $dataBuilder->build();
// You must change it to get your tokens
$tokens = MYDATABASE::pluck('fcm_token')->toArray();
$downstreamResponse = FCM::sendTo($tokens, $option, $notification, $data);
$downstreamResponse->numberSuccess();
$downstreamResponse->numberFailure();
$downstreamResponse->numberModification();
// return Array - you must remove all this tokens in your database
$downstreamResponse->tokensToDelete();
// return Array (key : oldToken, value : new token - you must change the token in your database)
$downstreamResponse->tokensToModify();
// return Array - you should try to resend the message to the tokens in the array
$downstreamResponse->tokensToRetry();
// return Array (key:token, value:error) - in production you should remove from your database the tokens present in this array
$downstreamResponse->tokensWithError();
```
> Kindly refer [Downstream message error response codes](https://firebase.google.com/docs/cloud-messaging/http-server-ref#error-codes) documentation for more information.
### Topics Messages
A topics message is a notification message, data message, or both, that you send to all the devices registered to this topic.
> Note: Topic names must be managed by your app and known by your server. The Laravel-FCM package or fcm doesn't provide an easy way to do that.
The following use statement is required for the examples below:
```php
use LaravelFCM\Message\Topics;
```
#### Sending a Message to a Topic
```php
$notificationBuilder = new PayloadNotificationBuilder('my title');
$notificationBuilder->setBody('Hello world')
->setSound('default');
$notification = $notificationBuilder->build();
$topic = new Topics();
$topic->topic('news');
$topicResponse = FCM::sendToTopic($topic, null, $notification, null);
$topicResponse->isSuccess();
$topicResponse->shouldRetry();
$topicResponse->error();
```
#### Sending a Message to Multiple Topics
It sends notification to devices registered at the following topics:
- news and economic
- news and cultural
> Note : Conditions for topics support two operators per expression
```php
$notificationBuilder = new PayloadNotificationBuilder('my title');
$notificationBuilder->setBody('Hello world')
->setSound('default');
$notification = $notificationBuilder->build();
$topic = new Topics();
$topic->topic('news')->andTopic(function($condition) {
$condition->topic('economic')->orTopic('cultural');
});
$topicResponse = FCM::sendToTopic($topic, null, $notification, null);
$topicResponse->isSuccess();
$topicResponse->shouldRetry();
$topicResponse->error());
```
### Group Messages
#### Sending a Notification to a Group
```php
$notificationKey = ['a_notification_key'];
$notificationBuilder = new PayloadNotificationBuilder('my title');
$notificationBuilder->setBody('Hello world')
->setSound('default');
$notification = $notificationBuilder->build();
$groupResponse = FCM::sendToGroup($notificationKey, null, $notification, null);
$groupResponse->numberSuccess();
$groupResponse->numberFailure();
$groupResponse->tokensFailed();
```
#### Creating a Group
```php
$tokens = ['a_registration_id_at_add_to_group'];
$groupName = "a_group";
$notificationKey
// Save notification key in your database you must use it to send messages or for managing this group
$notification_key = FCMGroup::createGroup($groupName, $tokens);
```
#### Adding Devices to a Group
```php
$tokens = ['a_registration_id_at_add_to_the_new_group'];
$groupName = "a_group";
$notificationKey = "notification_key_received_when_group_was_created";
$key = FCMGroup::addToGroup($groupName, $notificationKey, $tokens);
```
#### Deleting Devices from a Group
> Note if all devices are removed from the group, the group is automatically removed in "fcm".
```php
$tokens = ['a_registration_id_at_remove_from_the_group'];
$groupName = "a_group";
$notificationKey = "notification_key_received_when_group_was_created";
$key = FCMGroup::removeFromGroup($groupName, $notificationKey, $tokens);
```
## Options
Laravel-FCM supports options based on the options of Firebase Cloud Messaging. These options can help you to define the specificity of your notification.
You can construct an option as follows:
```php
$optionsBuilder = new OptionsBuilder();
$optionsBuilder->setTimeToLive(42*60)
->setCollapseKey('a_collapse_key');
$options = $optionsBuilder->build();
```
## Notification Messages
Notification payload is used to send a notification, the behaviour is defined by the App State and the OS of the receptor device.
**Notification messages are delivered to the notification tray when the app is in the background.** For apps in the foreground, messages are handled by these callbacks:
- didReceiveRemoteNotification: on iOS
- onMessageReceived() on Android. The notification key in the data bundle contains the notification.
See the [official documentation](https://firebase.google.com/docs/cloud-messaging/concept-options#notifications).
```php
$notificationBuilder = new PayloadNotificationBuilder();
$notificationBuilder->setTitle('title')
->setBody('body')
->setSound('sound')
->setBadge('badge');
$notification = $notificationBuilder->build();
```
## Data Messages
Set the data key with your custom key-value pairs to send a data payload to the client app. Data messages can have a 4KB maximum payload.
- **iOS**, FCM stores the message and delivers it **only when the app is in the foreground** and has established a FCM connection.
- **Android**, a client app receives a data message in onMessageReceived() and can handle the key-value pairs accordingly.
See the [official documentation](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages).
```php
$dataBuilder = new PayloadDataBuilder();
$dataBuilder->addData([
'data_1' => 'first_data'
]);
$data = $dataBuilder->build();
```
## Notification & Data Messages
App behavior when receiving messages that include both notification and data payloads depends on whether the app is in the background or the foreground—essentially, whether or not it is active at the time of receipt ([source](https://firebase.google.com/docs/cloud-messaging/concept-options#messages-with-both-notification-and-data-payloads)).
- **Background**, apps receive notification payload in the notification tray, and only handle the data payload when the user taps on the notification.
- **Foreground**, your app receives a message object with both payloads available.
## Topics
For topics message, Laravel-FCM offers an easy to use api which abstract firebase conditions. To make the condition given for example in the firebase official documentation it must be done with Laravel-FCM like below:
**Official documentation condition**
```
'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)
```
```php
$topics = new Topics();
$topics->topic('TopicA')
->andTopic(function($condition) {
$condition->topic('TopicB')->orTopic('TopicC');
});
```
## Testing
For integration testing, you can mock the responses with mockery and Mocks provided by the package.
There are 3 kinds of "MockResponse" given by the package:
- MockDownstreamResponse
- MockGroupResponse
- MockTopicResponse
You can mock the FCM call as in the following example:
```php
$numberSucess = 2;
$mockResponse = new \LaravelFCM\Mocks\MockDownstreamResponse(numberSucess);
$mockResponse->addTokenToDelete('token_to_delete');
$mockResponse->addTokenToModify('token_to_modify', 'token_modified');
$mockResponse->setMissingToken(true);
$sender = Mockery::mock(\LaravelFCM\Sender\FCMSender::class);
$sender->shouldReceive('sendTo')->once()->andReturn($mockResponse);
$this->app->singleton('fcm.sender', function($app) use($sender) {
return $sender;
});
```
## API Documentation
You can find more documentation about the API in the [API reference](./doc/Readme.md).
## Licence
This library is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).
Some of this documentation is coming from the official documentation. You can find it completely on the [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/) Website.

49
vendor/brozot/laravel-fcm/composer.json vendored Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "brozot/laravel-fcm",
"description": "Laravel / Lumen package for Firebase Cloud Messaging ",
"keywords": ["laravel", "lumen", "firebase", "notification", "push", "fcm", "firebase cloud messaging"],
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Nicolas Brosy",
"email": "nicolas.brosy@gmail.com"
}
],
"require": {
"php": "^8.0",
"illuminate/support": "5.*|^6|9.*",
"guzzlehttp/guzzle": "~7.0",
"monolog/monolog": "^1.12|^2.0"
},
"require-dev": {
"mockery/mockery" : "0.9.*",
"phpunit/phpunit" : "4.7.*",
"satooshi/php-coveralls": "dev-master",
"laravel/laravel": "5.2.*"
},
"autoload": {
"psr-4": {
"LaravelFCM\\": "src/",
"LaravelFCM\\Mocks\\": "tests/mocks"
}
},
"autoload-dev": {
"classmap": [
"tests/"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"laravel": {
"providers": [
"LaravelFCM\\FCMServiceProvider"
],
"aliases": {
"FCM": "LaravelFCM\\Facades\\FCM",
"FCMGroup": "LaravelFCM\\Facades\\FCMGroup"
}
}
}
}

3528
vendor/brozot/laravel-fcm/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
<?php
return [
'driver' => env('FCM_PROTOCOL', 'http'),
'log_enabled' => false,
'http' => [
'server_key' => env('FCM_SERVER_KEY', 'Your FCM server key'),
'sender_id' => env('FCM_SENDER_ID', 'Your sender id'),
'server_send_url' => 'https://fcm.googleapis.com/fcm/send',
'server_group_url' => 'https://android.googleapis.com/gcm/notification',
'timeout' => 30.0, // in second
],
];

View File

@@ -0,0 +1,19 @@
LaravelFCM\Message\Exceptions\InvalidOptionsException
===============
Class InvalidOptionsException
* Class name: InvalidOptionsException
* Namespace: LaravelFCM\Message\Exceptions
* Parent class: Exception

View File

@@ -0,0 +1,19 @@
LaravelFCM\Message\Exceptions\NoTopicProvidedException
===============
Class NoTopicProvidedException
* Class name: NoTopicProvidedException
* Namespace: LaravelFCM\Message\Exceptions
* Parent class: Exception

View File

@@ -0,0 +1,49 @@
LaravelFCM\Message\Options
===============
Class Options
* Class name: Options
* Namespace: LaravelFCM\Message
* This class implements: Illuminate\Contracts\Support\Arrayable
Methods
-------
### __construct
mixed LaravelFCM\Message\Options::__construct(\LaravelFCM\Message\OptionsBuilder $builder)
Options constructor.
* Visibility: **public**
#### Arguments
* $builder **[LaravelFCM\Message\OptionsBuilder](LaravelFCM-Message-OptionsBuilder.md)**
### toArray
array LaravelFCM\Message\Options::toArray()
Transform Option to array
* Visibility: **public**

View File

@@ -0,0 +1,241 @@
LaravelFCM\Message\OptionsBuilder
===============
Builder for creation of options used by FCM
Class OptionsBuilder
* Class name: OptionsBuilder
* Namespace: LaravelFCM\Message
Methods
-------
### setCollapseKey
\LaravelFCM\Message\OptionsBuilder LaravelFCM\Message\OptionsBuilder::setCollapseKey(String $collapseKey)
This parameter identifies a group of messages
A maximum of 4 different collapse keys is allowed at any given time.
* Visibility: **public**
#### Arguments
* $collapseKey **String**
### setPriority
\LaravelFCM\Message\OptionsBuilder LaravelFCM\Message\OptionsBuilder::setPriority(String $priority)
Sets the priority of the message. Valid values are "normal" and "high."
By default, messages are sent with normal priority
* Visibility: **public**
#### Arguments
* $priority **String**
### setContentAvailable
\LaravelFCM\Message\OptionsBuilder LaravelFCM\Message\OptionsBuilder::setContentAvailable(boolean $contentAvailable)
support only Android and Ios
An inactive client app is awoken.
On iOS, use this field to represent content-available in the APNS payload.
On Android, data messages wake the app by default.
On Chrome, currently not supported.
* Visibility: **public**
#### Arguments
* $contentAvailable **boolean**
### setDelayWhileIdle
\LaravelFCM\Message\OptionsBuilder LaravelFCM\Message\OptionsBuilder::setDelayWhileIdle(boolean $delayWhileIdle)
When this parameter is set to true, it indicates that the message should not be sent until the device becomes active.
* Visibility: **public**
#### Arguments
* $delayWhileIdle **boolean**
### setTimeToLive
\LaravelFCM\Message\OptionsBuilder LaravelFCM\Message\OptionsBuilder::setTimeToLive(integer $timeToLive)
This parameter specifies how long the message should be kept in FCM storage if the device is offline
* Visibility: **public**
#### Arguments
* $timeToLive **integer** - &lt;p&gt;(in second) min:0 max:2419200&lt;/p&gt;
### setRestrictedPackageName
\LaravelFCM\Message\OptionsBuilder LaravelFCM\Message\OptionsBuilder::setRestrictedPackageName(string $restrictedPackageName)
This parameter specifies the package name of the application where the registration tokens must match in order to receive the message.
* Visibility: **public**
#### Arguments
* $restrictedPackageName **string**
### setDryRun
\LaravelFCM\Message\OptionsBuilder LaravelFCM\Message\OptionsBuilder::setDryRun(boolean $isDryRun)
This parameter, when set to true, allows developers to test a request without actually sending a message.
It should only be used for the development
* Visibility: **public**
#### Arguments
* $isDryRun **boolean**
### getCollapseKey
null|string LaravelFCM\Message\OptionsBuilder::getCollapseKey()
Get the collapseKey
* Visibility: **public**
### getPriority
null|string LaravelFCM\Message\OptionsBuilder::getPriority()
Get the priority
* Visibility: **public**
### isContentAvailable
boolean LaravelFCM\Message\OptionsBuilder::isContentAvailable()
is content available
* Visibility: **public**
### isDelayWhileIdle
boolean LaravelFCM\Message\OptionsBuilder::isDelayWhileIdle()
is delay white idle
* Visibility: **public**
### getTimeToLive
null|integer LaravelFCM\Message\OptionsBuilder::getTimeToLive()
get time to live
* Visibility: **public**
### getRestrictedPackageName
null|string LaravelFCM\Message\OptionsBuilder::getRestrictedPackageName()
get restricted package name
* Visibility: **public**
### isDryRun
boolean LaravelFCM\Message\OptionsBuilder::isDryRun()
is dry run
* Visibility: **public**
### build
\LaravelFCM\Message\Options LaravelFCM\Message\OptionsBuilder::build()
build an instance of Options
* Visibility: **public**

View File

@@ -0,0 +1,69 @@
LaravelFCM\Message\OptionsPriorities
===============
Class OptionsPriorities
* Class name: OptionsPriorities
* Namespace: LaravelFCM\Message
Constants
----------
### high
const high = "high"
### normal
const normal = "normal"
Methods
-------
### getPriorities
array LaravelFCM\Message\OptionsPriorities::getPriorities()
* Visibility: **public**
* This method is **static**.
### isValid
boolean LaravelFCM\Message\OptionsPriorities::isValid($priority)
check if this priority is supported by fcm
* Visibility: **public**
* This method is **static**.
#### Arguments
* $priority **mixed**

View File

@@ -0,0 +1,49 @@
LaravelFCM\Message\PayloadData
===============
Class PayloadData
* Class name: PayloadData
* Namespace: LaravelFCM\Message
* This class implements: Illuminate\Contracts\Support\Arrayable
Methods
-------
### __construct
mixed LaravelFCM\Message\PayloadData::__construct(\LaravelFCM\Message\PayloadDataBuilder $builder)
PayloadData constructor.
* Visibility: **public**
#### Arguments
* $builder **[LaravelFCM\Message\PayloadDataBuilder](LaravelFCM-Message-PayloadDataBuilder.md)**
### toArray
array LaravelFCM\Message\PayloadData::toArray()
Transform payloadData to array
* Visibility: **public**

View File

@@ -0,0 +1,91 @@
LaravelFCM\Message\PayloadDataBuilder
===============
Class PayloadDataBuilder
Official google documentation :
* Class name: PayloadDataBuilder
* Namespace: LaravelFCM\Message
Methods
-------
### addData
\LaravelFCM\Message\PayloadDataBuilder LaravelFCM\Message\PayloadDataBuilder::addData(array $data)
add data to existing data
* Visibility: **public**
#### Arguments
* $data **array**
### setData
\LaravelFCM\Message\PayloadDataBuilder LaravelFCM\Message\PayloadDataBuilder::setData(array $data)
erase data with new data
* Visibility: **public**
#### Arguments
* $data **array**
### removeAllData
mixed LaravelFCM\Message\PayloadDataBuilder::removeAllData()
Remove all data
* Visibility: **public**
### getData
array LaravelFCM\Message\PayloadDataBuilder::getData()
return data
* Visibility: **public**
### build
\LaravelFCM\Message\PayloadData LaravelFCM\Message\PayloadDataBuilder::build()
generate a PayloadData
* Visibility: **public**

View File

@@ -0,0 +1,49 @@
LaravelFCM\Message\PayloadNotification
===============
Class PayloadNotification
* Class name: PayloadNotification
* Namespace: LaravelFCM\Message
* This class implements: Illuminate\Contracts\Support\Arrayable
Methods
-------
### __construct
mixed LaravelFCM\Message\PayloadNotification::__construct(\LaravelFCM\Message\PayloadNotificationBuilder $builder)
PayloadNotification constructor.
* Visibility: **public**
#### Arguments
* $builder **[LaravelFCM\Message\PayloadNotificationBuilder](LaravelFCM-Message-PayloadNotificationBuilder.md)**
### toArray
array LaravelFCM\Message\PayloadNotification::toArray()
convert PayloadNotification to array
* Visibility: **public**

View File

@@ -0,0 +1,400 @@
LaravelFCM\Message\PayloadNotificationBuilder
===============
Class PayloadNotificationBuilder
Official google documentation :
* Class name: PayloadNotificationBuilder
* Namespace: LaravelFCM\Message
Methods
-------
### __construct
mixed LaravelFCM\Message\PayloadNotificationBuilder::__construct(String $title)
Title must be present on android notification and ios (watch) notification
* Visibility: **public**
#### Arguments
* $title **String**
### setTitle
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setTitle(String $title)
Indicates notification title. This field is not visible on iOS phones and tablets.
but it is required for android
* Visibility: **public**
#### Arguments
* $title **String**
### setBody
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setBody(String $body)
Indicates notification body text.
* Visibility: **public**
#### Arguments
* $body **String**
### setIcon
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setIcon(String $icon)
Supported Android
Indicates notification icon. example : Sets value to myicon for drawable resource myicon.
* Visibility: **public**
#### Arguments
* $icon **String**
### setSound
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setSound(String $sound)
Indicates a sound to play when the device receives a notification.
Supports default or the filename of a sound resource bundled in the app.
* Visibility: **public**
#### Arguments
* $sound **String**
### setBadge
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setBadge(String $badge)
Supported Ios
Indicates the badge on the client app home icon.
* Visibility: **public**
#### Arguments
* $badge **String**
### setTag
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setTag(String $tag)
Supported Android
Indicates whether each notification results in a new entry in the notification drawer on Android.
If not set, each request creates a new notification.
If set, and a notification with the same tag is already being shown, the new notification replaces the existing one in the notification drawer.
* Visibility: **public**
#### Arguments
* $tag **String**
### setColor
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setColor(String $color)
Supported Android
Indicates color of the icon, expressed in #rrggbb format
* Visibility: **public**
#### Arguments
* $color **String**
### setClickAction
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setClickAction(String $action)
Indicates the action associated with a user click on the notification
* Visibility: **public**
#### Arguments
* $action **String**
### setTitleLocationKey
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setTitleLocationKey(String $titleKey)
Indicates the key to the title string for localization.
* Visibility: **public**
#### Arguments
* $titleKey **String**
### setTitleLocationArgs
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setTitleLocationArgs(mixed $titleArgs)
Indicates the string value to replace format specifiers in the title string for localization.
* Visibility: **public**
#### Arguments
* $titleArgs **mixed**
### setBodyLocationKey
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setBodyLocationKey(String $bodyKey)
Indicates the key to the body string for localization.
* Visibility: **public**
#### Arguments
* $bodyKey **String**
### setBodyLocationArgs
\LaravelFCM\Message\PayloadNotificationBuilder LaravelFCM\Message\PayloadNotificationBuilder::setBodyLocationArgs(mixed $bodyArgs)
Indicates the string value to replace format specifiers in the body string for localization.
* Visibility: **public**
#### Arguments
* $bodyArgs **mixed**
### getTitle
null|String LaravelFCM\Message\PayloadNotificationBuilder::getTitle()
Get title
* Visibility: **public**
### getBody
null|String LaravelFCM\Message\PayloadNotificationBuilder::getBody()
Get body
* Visibility: **public**
### getIcon
null|String LaravelFCM\Message\PayloadNotificationBuilder::getIcon()
Get Icon
* Visibility: **public**
### getSound
null|String LaravelFCM\Message\PayloadNotificationBuilder::getSound()
Get Sound
* Visibility: **public**
### getBadge
null|String LaravelFCM\Message\PayloadNotificationBuilder::getBadge()
Get Badge
* Visibility: **public**
### getTag
null|String LaravelFCM\Message\PayloadNotificationBuilder::getTag()
Get Tag
* Visibility: **public**
### getColor
null|String LaravelFCM\Message\PayloadNotificationBuilder::getColor()
Get Color
* Visibility: **public**
### getClickAction
null|String LaravelFCM\Message\PayloadNotificationBuilder::getClickAction()
Get ClickAction
* Visibility: **public**
### getBodyLocationKey
null|String LaravelFCM\Message\PayloadNotificationBuilder::getBodyLocationKey()
Get BodyLocationKey
* Visibility: **public**
### getBodyLocationArgs
null|String|array LaravelFCM\Message\PayloadNotificationBuilder::getBodyLocationArgs()
Get BodyLocationArgs
* Visibility: **public**
### getTitleLocationKey
string LaravelFCM\Message\PayloadNotificationBuilder::getTitleLocationKey()
Get TitleLocationKey
* Visibility: **public**
### getTitleLocationArgs
null|String|array LaravelFCM\Message\PayloadNotificationBuilder::getTitleLocationArgs()
GetTitleLocationArgs
* Visibility: **public**
### build
\LaravelFCM\Message\PayloadNotification LaravelFCM\Message\PayloadNotificationBuilder::build()
Build an PayloadNotification
* Visibility: **public**

View File

@@ -0,0 +1,134 @@
LaravelFCM\Message\Topics
===============
Class Topics
Create topic or a topic condition
* Class name: Topics
* Namespace: LaravelFCM\Message
Methods
-------
### topic
\LaravelFCM\Message\Topics LaravelFCM\Message\Topics::topic(string $first)
Add a topic, this method should be called before any conditional topic
* Visibility: **public**
#### Arguments
* $first **string** - &lt;p&gt;topicName&lt;/p&gt;
### orTopic
\LaravelFCM\Message\Topics LaravelFCM\Message\Topics::orTopic(string|\Closure $first)
Add a or condition to the precedent topic set
Parenthesis is a closure
Equivalent of this: **'TopicA' in topic' || 'TopicB' in topics**
```
$topic = new Topics();
$topic->topic('TopicA')
->orTopic('TopicB');
```
Equivalent of this: **'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)**
```
$topic = new Topics();
$topic->topic('TopicA')
->andTopic(function($condition) {
$condition->topic('TopicB')->orTopic('TopicC');
});
```
> Note: Only two operators per expression are supported by fcm
* Visibility: **public**
#### Arguments
* $first **string|Closure** - &lt;p&gt;topicName or closure&lt;/p&gt;
### andTopic
\LaravelFCM\Message\Topics LaravelFCM\Message\Topics::andTopic(string|\Closure $first)
Add a and condition to the precedent topic set
Parenthesis is a closure
Equivalent of this: **'TopicA' in topic' && 'TopicB' in topics**
```
$topic = new Topics();
$topic->topic('TopicA')
->anTopic('TopicB');
```
Equivalent of this: **'TopicA' in topics || ('TopicB' in topics && 'TopicC' in topics)**
```
$topic = new Topics();
$topic->topic('TopicA')
->orTopic(function($condition) {
$condition->topic('TopicB')->AndTopic('TopicC');
});
```
> Note: Only two operators per expression are supported by fcm
* Visibility: **public**
#### Arguments
* $first **string|Closure** - &lt;p&gt;topicName or closure&lt;/p&gt;
### build
array|string LaravelFCM\Message\Topics::build()
Transform to array
* Visibility: **public**
### hasOnlyOneTopic
boolean LaravelFCM\Message\Topics::hasOnlyOneTopic()
Check if only one topic was set
* Visibility: **public**

View File

@@ -0,0 +1,118 @@
LaravelFCM\Response\BaseResponse
===============
Class BaseResponse
* Class name: BaseResponse
* Namespace: LaravelFCM\Response
* This is an **abstract** class
Constants
----------
### SUCCESS
const SUCCESS = 'success'
### FAILURE
const FAILURE = 'failure'
### ERROR
const ERROR = "error"
### MESSAGE_ID
const MESSAGE_ID = "message_id"
Methods
-------
### __construct
mixed LaravelFCM\Response\BaseResponse::__construct(\GuzzleHttp\Psr7\Response $response)
BaseResponse constructor.
* Visibility: **public**
#### Arguments
* $response **GuzzleHttp\Psr7\Response**
### isJsonResponse
mixed LaravelFCM\Response\BaseResponse::isJsonResponse(\GuzzleHttp\Psr7\Response $response)
Check if the response given by fcm is parsable
* Visibility: **private**
#### Arguments
* $response **GuzzleHttp\Psr7\Response**
### parseResponse
mixed LaravelFCM\Response\BaseResponse::parseResponse(array $responseInJson)
parse the response
* Visibility: **protected**
* This method is **abstract**.
#### Arguments
* $responseInJson **array**
### logResponse
mixed LaravelFCM\Response\BaseResponse::logResponse()
Log the response
* Visibility: **protected**
* This method is **abstract**.

View File

@@ -0,0 +1,338 @@
LaravelFCM\Response\DownstreamResponse
===============
Class DownstreamResponse
* Class name: DownstreamResponse
* Namespace: LaravelFCM\Response
* Parent class: [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
* This class implements: [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
Constants
----------
### MULTICAST_ID
const MULTICAST_ID = 'multicast_id'
### CANONICAL_IDS
const CANONICAL_IDS = "canonical_ids"
### RESULTS
const RESULTS = "results"
### MISSING_REGISTRATION
const MISSING_REGISTRATION = "MissingRegistration"
### MESSAGE_ID
const MESSAGE_ID = "message_id"
### REGISTRATION_ID
const REGISTRATION_ID = "registration_id"
### NOT_REGISTERED
const NOT_REGISTERED = "NotRegistered"
### INVALID_REGISTRATION
const INVALID_REGISTRATION = "InvalidRegistration"
### UNAVAILABLE
const UNAVAILABLE = "Unavailable"
### DEVICE_MESSAGE_RATE_EXCEEDED
const DEVICE_MESSAGE_RATE_EXCEEDED = "DeviceMessageRateExceeded"
### INTERNAL_SERVER_ERROR
const INTERNAL_SERVER_ERROR = "InternalServerError"
### SUCCESS
const SUCCESS = 'success'
### FAILURE
const FAILURE = 'failure'
### ERROR
const ERROR = "error"
Methods
-------
### __construct
mixed LaravelFCM\Response\BaseResponse::__construct(\GuzzleHttp\Psr7\Response $response)
BaseResponse constructor.
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
#### Arguments
* $response **GuzzleHttp\Psr7\Response**
### parseResponse
mixed LaravelFCM\Response\BaseResponse::parseResponse(array $responseInJson)
parse the response
* Visibility: **protected**
* This method is **abstract**.
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
#### Arguments
* $responseInJson **array**
### merge
mixed LaravelFCM\Response\DownstreamResponseContract::merge(\LaravelFCM\Response\DownstreamResponse $response)
Merge two response
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
#### Arguments
* $response **[LaravelFCM\Response\DownstreamResponse](LaravelFCM-Response-DownstreamResponse.md)**
### numberSuccess
integer LaravelFCM\Response\DownstreamResponseContract::numberSuccess()
Get the number of device reached with success
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### numberFailure
integer LaravelFCM\Response\DownstreamResponseContract::numberFailure()
Get the number of device which thrown an error
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### numberModification
integer LaravelFCM\Response\DownstreamResponseContract::numberModification()
Get the number of device that you need to modify their token
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### tokensToDelete
array LaravelFCM\Response\DownstreamResponseContract::tokensToDelete()
get token to delete
remove all tokens returned by this method in your database
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### tokensToModify
array LaravelFCM\Response\DownstreamResponseContract::tokensToModify()
get token to modify
key: oldToken
value: new token
find the old token in your database and replace it with the new one
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### tokensToRetry
array LaravelFCM\Response\DownstreamResponseContract::tokensToRetry()
Get tokens that you should resend using exponential backoof
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### tokensWithError
array LaravelFCM\Response\DownstreamResponseContract::tokensWithError()
Get tokens that thrown an error
key : token
value : error
In production, remove these tokens from you database
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### hasMissingToken
boolean LaravelFCM\Response\DownstreamResponseContract::hasMissingToken()
check if missing tokens was given to the request
If true, remove all the empty token in your database
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### isJsonResponse
mixed LaravelFCM\Response\BaseResponse::isJsonResponse(\GuzzleHttp\Psr7\Response $response)
Check if the response given by fcm is parsable
* Visibility: **private**
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
#### Arguments
* $response **GuzzleHttp\Psr7\Response**
### logResponse
mixed LaravelFCM\Response\BaseResponse::logResponse()
Log the response
* Visibility: **protected**
* This method is **abstract**.
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)

View File

@@ -0,0 +1,147 @@
LaravelFCM\Response\DownstreamResponseContract
===============
Interface DownstreamResponseContract
* Interface name: DownstreamResponseContract
* Namespace: LaravelFCM\Response
* This is an **interface**
Methods
-------
### merge
mixed LaravelFCM\Response\DownstreamResponseContract::merge(\LaravelFCM\Response\DownstreamResponse $response)
Merge two response
* Visibility: **public**
#### Arguments
* $response **[LaravelFCM\Response\DownstreamResponse](LaravelFCM-Response-DownstreamResponse.md)**
### numberSuccess
integer LaravelFCM\Response\DownstreamResponseContract::numberSuccess()
Get the number of device reached with success
* Visibility: **public**
### numberFailure
integer LaravelFCM\Response\DownstreamResponseContract::numberFailure()
Get the number of device which thrown an error
* Visibility: **public**
### numberModification
integer LaravelFCM\Response\DownstreamResponseContract::numberModification()
Get the number of device that you need to modify their token
* Visibility: **public**
### tokensToDelete
array LaravelFCM\Response\DownstreamResponseContract::tokensToDelete()
get token to delete
remove all tokens returned by this method in your database
* Visibility: **public**
### tokensToModify
array LaravelFCM\Response\DownstreamResponseContract::tokensToModify()
get token to modify
key: oldToken
value: new token
find the old token in your database and replace it with the new one
* Visibility: **public**
### tokensToRetry
array LaravelFCM\Response\DownstreamResponseContract::tokensToRetry()
Get tokens that you should resend using exponential backoof
* Visibility: **public**
### tokensWithError
array LaravelFCM\Response\DownstreamResponseContract::tokensWithError()
Get tokens that thrown an error
key : token
value : error
In production, remove these tokens from you database
* Visibility: **public**
### hasMissingToken
boolean LaravelFCM\Response\DownstreamResponseContract::hasMissingToken()
check if missing tokens was given to the request
If true, remove all the empty token in your database
* Visibility: **public**

View File

@@ -0,0 +1,37 @@
LaravelFCM\Response\Exceptions\InvalidRequestException
===============
Class InvalidRequestException
* Class name: InvalidRequestException
* Namespace: LaravelFCM\Response\Exceptions
* Parent class: Exception
Methods
-------
### __construct
mixed LaravelFCM\Response\Exceptions\InvalidRequestException::__construct(\GuzzleHttp\Psr7\Response $response)
InvalidRequestException constructor.
* Visibility: **public**
#### Arguments
* $response **GuzzleHttp\Psr7\Response**

View File

@@ -0,0 +1,50 @@
LaravelFCM\Response\Exceptions\ServerResponseException
===============
Class ServerResponseException
* Class name: ServerResponseException
* Namespace: LaravelFCM\Response\Exceptions
* Parent class: Exception
Properties
----------
### $retryAfter
public integer $retryAfter
retry after
* Visibility: **public**
Methods
-------
### __construct
mixed LaravelFCM\Response\Exceptions\ServerResponseException::__construct(\GuzzleHttp\Psr7\Response $response)
ServerResponseException constructor.
* Visibility: **public**
#### Arguments
* $response **GuzzleHttp\Psr7\Response**

View File

@@ -0,0 +1,37 @@
LaravelFCM\Response\Exceptions\UnauthorizedRequestException
===============
Class UnauthorizedRequestException
* Class name: UnauthorizedRequestException
* Namespace: LaravelFCM\Response\Exceptions
* Parent class: Exception
Methods
-------
### __construct
mixed LaravelFCM\Response\Exceptions\UnauthorizedRequestException::__construct(\GuzzleHttp\Psr7\Response $response)
UnauthorizedRequestException constructor.
* Visibility: **public**
#### Arguments
* $response **GuzzleHttp\Psr7\Response**

View File

@@ -0,0 +1,172 @@
LaravelFCM\Response\GroupResponse
===============
Class GroupResponse
* Class name: GroupResponse
* Namespace: LaravelFCM\Response
* Parent class: [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
* This class implements: [LaravelFCM\Response\GroupResponseContract](LaravelFCM-Response-GroupResponseContract.md)
Constants
----------
### FAILED_REGISTRATION_IDS
const FAILED_REGISTRATION_IDS = "failed_registration_ids"
### SUCCESS
const SUCCESS = 'success'
### FAILURE
const FAILURE = 'failure'
### ERROR
const ERROR = "error"
### MESSAGE_ID
const MESSAGE_ID = "message_id"
Methods
-------
### __construct
mixed LaravelFCM\Response\BaseResponse::__construct(\GuzzleHttp\Psr7\Response $response)
BaseResponse constructor.
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
#### Arguments
* $response **GuzzleHttp\Psr7\Response**
### parseResponse
mixed LaravelFCM\Response\BaseResponse::parseResponse(array $responseInJson)
parse the response
* Visibility: **protected**
* This method is **abstract**.
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
#### Arguments
* $responseInJson **array**
### logResponse
mixed LaravelFCM\Response\BaseResponse::logResponse()
Log the response
* Visibility: **protected**
* This method is **abstract**.
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
### numberSuccess
integer LaravelFCM\Response\GroupResponseContract::numberSuccess()
Get the number of device reached with success
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\GroupResponseContract](LaravelFCM-Response-GroupResponseContract.md)
### numberFailure
integer LaravelFCM\Response\GroupResponseContract::numberFailure()
Get the number of device which thrown an error
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\GroupResponseContract](LaravelFCM-Response-GroupResponseContract.md)
### tokensFailed
array LaravelFCM\Response\GroupResponseContract::tokensFailed()
Get all token in group that fcm cannot reach
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\GroupResponseContract](LaravelFCM-Response-GroupResponseContract.md)
### isJsonResponse
mixed LaravelFCM\Response\BaseResponse::isJsonResponse(\GuzzleHttp\Psr7\Response $response)
Check if the response given by fcm is parsable
* Visibility: **private**
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
#### Arguments
* $response **GuzzleHttp\Psr7\Response**

View File

@@ -0,0 +1,59 @@
LaravelFCM\Response\GroupResponseContract
===============
Interface GroupResponseContract
* Interface name: GroupResponseContract
* Namespace: LaravelFCM\Response
* This is an **interface**
Methods
-------
### numberSuccess
integer LaravelFCM\Response\GroupResponseContract::numberSuccess()
Get the number of device reached with success
* Visibility: **public**
### numberFailure
integer LaravelFCM\Response\GroupResponseContract::numberFailure()
Get the number of device which thrown an error
* Visibility: **public**
### tokensFailed
array LaravelFCM\Response\GroupResponseContract::tokensFailed()
Get all token in group that fcm cannot reach
* Visibility: **public**

View File

@@ -0,0 +1,173 @@
LaravelFCM\Response\TopicResponse
===============
Class TopicResponse
* Class name: TopicResponse
* Namespace: LaravelFCM\Response
* Parent class: [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
* This class implements: [LaravelFCM\Response\TopicResponseContract](LaravelFCM-Response-TopicResponseContract.md)
Constants
----------
### LIMIT_RATE_TOPICS_EXCEEDED
const LIMIT_RATE_TOPICS_EXCEEDED = "TopicsMessageRateExceeded"
### SUCCESS
const SUCCESS = 'success'
### FAILURE
const FAILURE = 'failure'
### ERROR
const ERROR = "error"
### MESSAGE_ID
const MESSAGE_ID = "message_id"
Methods
-------
### __construct
mixed LaravelFCM\Response\BaseResponse::__construct(\GuzzleHttp\Psr7\Response $response)
BaseResponse constructor.
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
#### Arguments
* $response **GuzzleHttp\Psr7\Response**
### parseResponse
mixed LaravelFCM\Response\BaseResponse::parseResponse(array $responseInJson)
parse the response
* Visibility: **protected**
* This method is **abstract**.
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
#### Arguments
* $responseInJson **array**
### logResponse
mixed LaravelFCM\Response\BaseResponse::logResponse()
Log the response
* Visibility: **protected**
* This method is **abstract**.
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
### isSuccess
boolean LaravelFCM\Response\TopicResponseContract::isSuccess()
true if topic sent with success
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\TopicResponseContract](LaravelFCM-Response-TopicResponseContract.md)
### error
string LaravelFCM\Response\TopicResponseContract::error()
return error message
you should test if it's necessary to resent it
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\TopicResponseContract](LaravelFCM-Response-TopicResponseContract.md)
### shouldRetry
boolean LaravelFCM\Response\TopicResponseContract::shouldRetry()
return true if it's necessary resent it using exponential backoff
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\TopicResponseContract](LaravelFCM-Response-TopicResponseContract.md)
### isJsonResponse
mixed LaravelFCM\Response\BaseResponse::isJsonResponse(\GuzzleHttp\Psr7\Response $response)
Check if the response given by fcm is parsable
* Visibility: **private**
* This method is defined by [LaravelFCM\Response\BaseResponse](LaravelFCM-Response-BaseResponse.md)
#### Arguments
* $response **GuzzleHttp\Psr7\Response**

View File

@@ -0,0 +1,60 @@
LaravelFCM\Response\TopicResponseContract
===============
Interface TopicResponseContract
* Interface name: TopicResponseContract
* Namespace: LaravelFCM\Response
* This is an **interface**
Methods
-------
### isSuccess
boolean LaravelFCM\Response\TopicResponseContract::isSuccess()
true if topic sent with success
* Visibility: **public**
### error
string LaravelFCM\Response\TopicResponseContract::error()
return error message
you should test if it's necessary to resent it
* Visibility: **public**
### shouldRetry
boolean LaravelFCM\Response\TopicResponseContract::shouldRetry()
return true if it's necessary resent it using exponential backoff
* Visibility: **public**

View File

@@ -0,0 +1,83 @@
LaravelFCM\Sender\BaseSender
===============
Class BaseSender
* Class name: BaseSender
* Namespace: LaravelFCM\Sender
* This is an **abstract** class
Properties
----------
### $client
protected \Illuminate\Foundation\Application $client
Guzzle Client
* Visibility: **protected**
### $config
protected array $config
configuration
* Visibility: **protected**
### $url
protected mixed $url
url
* Visibility: **protected**
Methods
-------
### __construct
mixed LaravelFCM\Sender\BaseSender::__construct()
BaseSender constructor.
* Visibility: **public**
### getUrl
string LaravelFCM\Sender\BaseSender::getUrl()
get the url
* Visibility: **protected**
* This method is **abstract**.

View File

@@ -0,0 +1,164 @@
LaravelFCM\Sender\FCMGroup
===============
Class FCMGroup
* Class name: FCMGroup
* Namespace: LaravelFCM\Sender
* Parent class: [LaravelFCM\Sender\BaseSender](LaravelFCM-Sender-BaseSender.md)
Constants
----------
### CREATE
const CREATE = "create"
### ADD
const ADD = "add"
### REMOVE
const REMOVE = "remove"
Properties
----------
### $client
protected \Illuminate\Foundation\Application $client
Guzzle Client
* Visibility: **protected**
### $config
protected array $config
configuration
* Visibility: **protected**
### $url
protected mixed $url
url
* Visibility: **protected**
Methods
-------
### createGroup
null LaravelFCM\Sender\FCMGroup::createGroup($notificationKeyName, array $registrationIds)
Create a group
* Visibility: **public**
#### Arguments
* $notificationKeyName **mixed**
* $registrationIds **array**
### addToGroup
null LaravelFCM\Sender\FCMGroup::addToGroup($notificationKeyName, $notificationKey, array $registrationIds)
add registrationId to a existing group
* Visibility: **public**
#### Arguments
* $notificationKeyName **mixed**
* $notificationKey **mixed**
* $registrationIds **array** - &lt;p&gt;registrationIds to add&lt;/p&gt;
### removeFromGroup
null LaravelFCM\Sender\FCMGroup::removeFromGroup($notificationKeyName, $notificationKey, array $registeredIds)
remove registrationId to a existing group
>Note: if you remove all registrationIds the group is automatically deleted
* Visibility: **public**
#### Arguments
* $notificationKeyName **mixed**
* $notificationKey **mixed**
* $registeredIds **array** - &lt;p&gt;registrationIds to remove&lt;/p&gt;
### getUrl
string LaravelFCM\Sender\BaseSender::getUrl()
get the url
* Visibility: **protected**
* This method is **abstract**.
* This method is defined by [LaravelFCM\Sender\BaseSender](LaravelFCM-Sender-BaseSender.md)
### __construct
mixed LaravelFCM\Sender\BaseSender::__construct()
BaseSender constructor.
* Visibility: **public**
* This method is defined by [LaravelFCM\Sender\BaseSender](LaravelFCM-Sender-BaseSender.md)

View File

@@ -0,0 +1,153 @@
LaravelFCM\Sender\FCMSender
===============
Class FCMSender
* Class name: FCMSender
* Namespace: LaravelFCM\Sender
* Parent class: [LaravelFCM\Sender\BaseSender](LaravelFCM-Sender-BaseSender.md)
Constants
----------
### MAX_TOKEN_PER_REQUEST
const MAX_TOKEN_PER_REQUEST = 1000
Properties
----------
### $client
protected \Illuminate\Foundation\Application $client
Guzzle Client
* Visibility: **protected**
### $config
protected array $config
configuration
* Visibility: **protected**
### $url
protected mixed $url
url
* Visibility: **protected**
Methods
-------
### sendTo
\LaravelFCM\Response\DownstreamResponse|null LaravelFCM\Sender\FCMSender::sendTo(String|array $to, \LaravelFCM\Message\Options|null $options, \LaravelFCM\Message\PayloadNotification|null $notification, \LaravelFCM\Message\PayloadData|null $data)
send a downstream message to
- a unique device with is registration Token
- or to multiples devices with an array of registrationIds
* Visibility: **public**
#### Arguments
* $to **String|array**
* $options **[LaravelFCM\Message\Options](LaravelFCM-Message-Options.md)|null**
* $notification **[LaravelFCM\Message\PayloadNotification](LaravelFCM-Message-PayloadNotification.md)|null**
* $data **[LaravelFCM\Message\PayloadData](LaravelFCM-Message-PayloadData.md)|null**
### sendToGroup
\LaravelFCM\Response\GroupResponse LaravelFCM\Sender\FCMSender::sendToGroup($notificationKey, \LaravelFCM\Message\Options|null $options, \LaravelFCM\Message\PayloadNotification|null $notification, \LaravelFCM\Message\PayloadData|null $data)
Send a message to a group of devices identified with them notification key
* Visibility: **public**
#### Arguments
* $notificationKey **mixed**
* $options **[LaravelFCM\Message\Options](LaravelFCM-Message-Options.md)|null**
* $notification **[LaravelFCM\Message\PayloadNotification](LaravelFCM-Message-PayloadNotification.md)|null**
* $data **[LaravelFCM\Message\PayloadData](LaravelFCM-Message-PayloadData.md)|null**
### sendToTopic
\LaravelFCM\Response\TopicResponse LaravelFCM\Sender\FCMSender::sendToTopic(\LaravelFCM\Message\Topics $topics, \LaravelFCM\Message\Options|null $options, \LaravelFCM\Message\PayloadNotification|null $notification, \LaravelFCM\Message\PayloadData|null $data)
Send message devices registered at a or more topics
* Visibility: **public**
#### Arguments
* $topics **[LaravelFCM\Message\Topics](LaravelFCM-Message-Topics.md)**
* $options **[LaravelFCM\Message\Options](LaravelFCM-Message-Options.md)|null**
* $notification **[LaravelFCM\Message\PayloadNotification](LaravelFCM-Message-PayloadNotification.md)|null**
* $data **[LaravelFCM\Message\PayloadData](LaravelFCM-Message-PayloadData.md)|null**
### getUrl
string LaravelFCM\Sender\BaseSender::getUrl()
get the url
* Visibility: **protected**
* This method is **abstract**.
* This method is defined by [LaravelFCM\Sender\BaseSender](LaravelFCM-Sender-BaseSender.md)
### __construct
mixed LaravelFCM\Sender\BaseSender::__construct()
BaseSender constructor.
* Visibility: **public**
* This method is defined by [LaravelFCM\Sender\BaseSender](LaravelFCM-Sender-BaseSender.md)

View File

@@ -0,0 +1,254 @@
LaravelFCM\Test\Mocks\MockDownstreamResponse
===============
Class MockDownstreamResponse **Only use it for testing**
* Class name: MockDownstreamResponse
* Namespace: LaravelFCM\Test\Mocks
* This class implements: [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
Methods
-------
### __construct
mixed LaravelFCM\Test\Mocks\MockDownstreamResponse::__construct($numberSuccess)
DownstreamResponse constructor.
* Visibility: **public**
#### Arguments
* $numberSuccess **mixed**
### merge
mixed LaravelFCM\Response\DownstreamResponseContract::merge(\LaravelFCM\Response\DownstreamResponse $response)
Merge two response
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
#### Arguments
* $response **[LaravelFCM\Response\DownstreamResponse](LaravelFCM-Response-DownstreamResponse.md)**
### numberSuccess
integer LaravelFCM\Response\DownstreamResponseContract::numberSuccess()
Get the number of device reached with success
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### numberFailure
integer LaravelFCM\Response\DownstreamResponseContract::numberFailure()
Get the number of device which thrown an error
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### numberModification
integer LaravelFCM\Response\DownstreamResponseContract::numberModification()
Get the number of device that you need to modify their token
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### addTokenToDelete
mixed LaravelFCM\Test\Mocks\MockDownstreamResponse::addTokenToDelete($token)
Add a token to delete
* Visibility: **public**
#### Arguments
* $token **mixed**
### tokensToDelete
array LaravelFCM\Response\DownstreamResponseContract::tokensToDelete()
get token to delete
remove all tokens returned by this method in your database
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### addTokenToModify
mixed LaravelFCM\Test\Mocks\MockDownstreamResponse::addTokenToModify($oldToken, $newToken)
Add a token to modify
* Visibility: **public**
#### Arguments
* $oldToken **mixed**
* $newToken **mixed**
### tokensToModify
array LaravelFCM\Response\DownstreamResponseContract::tokensToModify()
get token to modify
key: oldToken
value: new token
find the old token in your database and replace it with the new one
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### addTokenToRetry
mixed LaravelFCM\Test\Mocks\MockDownstreamResponse::addTokenToRetry($token)
Add a token to retry
* Visibility: **public**
#### Arguments
* $token **mixed**
### tokensToRetry
array LaravelFCM\Response\DownstreamResponseContract::tokensToRetry()
Get tokens that you should resend using exponential backoof
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### addTokenWithError
mixed LaravelFCM\Test\Mocks\MockDownstreamResponse::addTokenWithError($token, $message)
Add a token to errors
* Visibility: **public**
#### Arguments
* $token **mixed**
* $message **mixed**
### tokensWithError
array LaravelFCM\Response\DownstreamResponseContract::tokensWithError()
Get tokens that thrown an error
key : token
value : error
In production, remove these tokens from you database
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
### setMissingToken
mixed LaravelFCM\Test\Mocks\MockDownstreamResponse::setMissingToken($hasMissingToken)
change missing token state
* Visibility: **public**
#### Arguments
* $hasMissingToken **mixed**
### hasMissingToken
boolean LaravelFCM\Response\DownstreamResponseContract::hasMissingToken()
check if missing tokens was given to the request
If true, remove all the empty token in your database
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)

View File

@@ -0,0 +1,110 @@
LaravelFCM\Test\Mocks\MockGroupResponse
===============
Class MockGroupResponse **Only use it for testing**
* Class name: MockGroupResponse
* Namespace: LaravelFCM\Test\Mocks
* This class implements: [LaravelFCM\Response\GroupResponseContract](LaravelFCM-Response-GroupResponseContract.md)
Methods
-------
### setNumberSuccess
mixed LaravelFCM\Test\Mocks\MockGroupResponse::setNumberSuccess($numberSuccess)
set number of success
* Visibility: **public**
#### Arguments
* $numberSuccess **mixed**
### numberSuccess
integer LaravelFCM\Response\GroupResponseContract::numberSuccess()
Get the number of device reached with success
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\GroupResponseContract](LaravelFCM-Response-GroupResponseContract.md)
### setNumberFailure
mixed LaravelFCM\Test\Mocks\MockGroupResponse::setNumberFailure($numberFailures)
set number of failures
* Visibility: **public**
#### Arguments
* $numberFailures **mixed**
### numberFailure
integer LaravelFCM\Response\GroupResponseContract::numberFailure()
Get the number of device which thrown an error
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\GroupResponseContract](LaravelFCM-Response-GroupResponseContract.md)
### addTokenFailed
mixed LaravelFCM\Test\Mocks\MockGroupResponse::addTokenFailed($tokenFailed)
add a token to the failed list
* Visibility: **public**
#### Arguments
* $tokenFailed **mixed**
### tokensFailed
array LaravelFCM\Response\GroupResponseContract::tokensFailed()
Get all token in group that fcm cannot reach
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\GroupResponseContract](LaravelFCM-Response-GroupResponseContract.md)

View File

@@ -0,0 +1,95 @@
LaravelFCM\Test\Mocks\MockTopicResponse
===============
Class MockTopicResponse **Only use it for testing**
* Class name: MockTopicResponse
* Namespace: LaravelFCM\Test\Mocks
* This class implements: [LaravelFCM\Response\TopicResponseContract](LaravelFCM-Response-TopicResponseContract.md)
Methods
-------
### setSuccess
mixed LaravelFCM\Test\Mocks\MockTopicResponse::setSuccess($messageId)
if success set a message id
* Visibility: **public**
#### Arguments
* $messageId **mixed**
### isSuccess
boolean LaravelFCM\Response\TopicResponseContract::isSuccess()
true if topic sent with success
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\TopicResponseContract](LaravelFCM-Response-TopicResponseContract.md)
### setError
mixed LaravelFCM\Test\Mocks\MockTopicResponse::setError($error)
set error
* Visibility: **public**
#### Arguments
* $error **mixed**
### error
string LaravelFCM\Response\TopicResponseContract::error()
return error message
you should test if it's necessary to resent it
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\TopicResponseContract](LaravelFCM-Response-TopicResponseContract.md)
### shouldRetry
boolean LaravelFCM\Response\TopicResponseContract::shouldRetry()
return true if it's necessary resent it using exponential backoff
* Visibility: **public**
* This method is defined by [LaravelFCM\Response\TopicResponseContract](LaravelFCM-Response-TopicResponseContract.md)

38
vendor/brozot/laravel-fcm/doc/Readme.md vendored Normal file
View File

@@ -0,0 +1,38 @@
API Index
=========
* LaravelFCM
* LaravelFCM\Message
* LaravelFCM\Message\Exceptions
* [InvalidOptionsException](LaravelFCM-Message-Exceptions-InvalidOptionsException.md)
* [NoTopicProvidedException](LaravelFCM-Message-Exceptions-NoTopicProvidedException.md)
* [Options](LaravelFCM-Message-Options.md)
* [OptionsBuilder](LaravelFCM-Message-OptionsBuilder.md)
* [OptionsPriorities](LaravelFCM-Message-OptionsPriorities.md)
* [PayloadData](LaravelFCM-Message-PayloadData.md)
* [PayloadDataBuilder](LaravelFCM-Message-PayloadDataBuilder.md)
* [PayloadNotification](LaravelFCM-Message-PayloadNotification.md)
* [PayloadNotificationBuilder](LaravelFCM-Message-PayloadNotificationBuilder.md)
* [Topics](LaravelFCM-Message-Topics.md)
* LaravelFCM\Sender
* [BaseSender](LaravelFCM-Sender-BaseSender.md)
* [FCMGroup](LaravelFCM-Sender-FCMGroup.md)
* [FCMSender](LaravelFCM-Sender-FCMSender.md)
* LaravelFCM\Response
* [BaseResponse](LaravelFCM-Response-BaseResponse.md)
* [DownstreamResponse](LaravelFCM-Response-DownstreamResponse.md)
* [DownstreamResponseContract](LaravelFCM-Response-DownstreamResponseContract.md)
* LaravelFCM\Response\Exceptions
* [InvalidRequestException](LaravelFCM-Response-Exceptions-InvalidRequestException.md)
* [ServerResponseException](LaravelFCM-Response-Exceptions-ServerResponseException.md)
* [UnauthorizedRequestException](LaravelFCM-Response-Exceptions-UnauthorizedRequestException.md)
* [GroupResponse](LaravelFCM-Response-GroupResponse.md)
* [GroupResponseContract](LaravelFCM-Response-GroupResponseContract.md)
* [TopicResponse](LaravelFCM-Response-TopicResponse.md)
* [TopicResponseContract](LaravelFCM-Response-TopicResponseContract.md)
* LaravelFCM\Test
* LaravelFCM\Test\Mocks
* [MockDownstreamResponse](LaravelFCM-Test-Mocks-MockDownstreamResponse.md)
* [MockGroupResponse](LaravelFCM-Test-Mocks-MockGroupResponse.md)
* [MockTopicResponse](LaravelFCM-Test-Mocks-MockTopicResponse.md)

18
vendor/brozot/laravel-fcm/phpunit.xml vendored Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false">
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -0,0 +1,21 @@
<?php
namespace LaravelFCM;
use GuzzleHttp\Client;
use Illuminate\Support\Manager;
class FCMManager extends Manager
{
public function getDefaultDriver()
{
return $this->app[ 'config' ][ 'fcm.driver' ];
}
protected function createHttpDriver()
{
$config = $this->app[ 'config' ]->get('fcm.http', []);
return new Client(['timeout' => $config[ 'timeout' ]]);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace LaravelFCM;
use Illuminate\Support\Str;
use LaravelFCM\Sender\FCMGroup;
use LaravelFCM\Sender\FCMSender;
use Illuminate\Support\ServiceProvider;
class FCMServiceProvider extends ServiceProvider
{
protected $defer = true;
public function boot()
{
if (Str::contains($this->app->version(), 'Lumen')) {
$this->app->configure('fcm');
} else {
$this->publishes([
__DIR__.'/../config/fcm.php' => config_path('fcm.php'),
]);
}
}
public function register()
{
if (!Str::contains($this->app->version(), 'Lumen')) {
$this->mergeConfigFrom(__DIR__.'/../config/fcm.php', 'fcm');
}
$this->app->singleton('fcm.client', function ($app) {
return (new FCMManager($app))->driver();
});
$this->app->bind('fcm.group', function ($app) {
$client = $app[ 'fcm.client' ];
$url = $app[ 'config' ]->get('fcm.http.server_group_url');
return new FCMGroup($client, $url);
});
$this->app->bind('fcm.sender', function ($app) {
$client = $app[ 'fcm.client' ];
$url = $app[ 'config' ]->get('fcm.http.server_send_url');
return new FCMSender($client, $url);
});
}
public function provides()
{
return ['fcm.client', 'fcm.group', 'fcm.sender'];
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace LaravelFCM\Facades;
use Illuminate\Support\Facades\Facade;
class FCM extends Facade
{
protected static function getFacadeAccessor()
{
return 'fcm.sender';
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace LaravelFCM\Facades;
use Illuminate\Support\Facades\Facade;
class FCMGroup extends Facade
{
protected static function getFacadeAccessor()
{
return 'fcm.group';
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace LaravelFCM\Message\Exceptions;
use Exception;
/**
* Class InvalidOptionsException.
*/
class InvalidOptionsException extends Exception
{
}

View File

@@ -0,0 +1,12 @@
<?php
namespace LaravelFCM\Message\Exceptions;
use Exception;
/**
* Class NoTopicProvidedException.
*/
class NoTopicProvidedException extends Exception
{
}

View File

@@ -0,0 +1,110 @@
<?php
namespace LaravelFCM\Message;
use Illuminate\Contracts\Support\Arrayable;
/**
* Class Options.
*/
class Options implements Arrayable
{
/**
* @internal
*
* @var null|string
*/
protected $collapseKey;
/**
* @internal
*
* @var null|string
*/
protected $priority;
/**
* @internal
*
* @var bool
*/
protected $contentAvailable;
/**
* @internal
*
* @var bool
*/
protected $isMutableContent = false;
/**
* @internal
*
* @var bool
*/
protected $delayWhileIdle;
/**
* @internal
*
* @var int|null
*/
protected $timeToLive;
/**
* @internal
*
* @var null|string
*/
protected $restrictedPackageName;
/**
* @internal
*
* @var bool
*/
protected $isDryRun = false;
/**
* Options constructor.
*
* @param OptionsBuilder $builder
*/
public function __construct(OptionsBuilder $builder)
{
$this->collapseKey = $builder->getCollapseKey();
$this->priority = $builder->getPriority();
$this->contentAvailable = $builder->isContentAvailable();
$this->isMutableContent = $builder->isMutableContent();
$this->delayWhileIdle = $builder->isDelayWhileIdle();
$this->timeToLive = $builder->getTimeToLive();
$this->restrictedPackageName = $builder->getRestrictedPackageName();
$this->isDryRun = $builder->isDryRun();
}
/**
* Transform Option to array.
*
* @return array
*/
public function toArray()
{
$contentAvailable = $this->contentAvailable ? true : null;
$mutableContent = $this->isMutableContent ? true : null;
$delayWhileIdle = $this->delayWhileIdle ? true : null;
$dryRun = $this->isDryRun ? true : null;
$options = [
'collapse_key' => $this->collapseKey,
'priority' => $this->priority,
'content_available' => $contentAvailable,
'mutable_content' => $mutableContent,
'delay_while_idle' => $delayWhileIdle,
'time_to_live' => $this->timeToLive,
'restricted_package_name' => $this->restrictedPackageName,
'dry_run' => $dryRun,
];
return array_filter($options);
}
}

View File

@@ -0,0 +1,336 @@
<?php
namespace LaravelFCM\Message;
use LaravelFCM\Message\Exceptions\InvalidOptionsException;
use ReflectionClass;
/**
* Builder for creation of options used by FCM.
*
* Class OptionsBuilder
*
* @link http://firebase.google.com/docs/cloud-messaging/http-server-ref#downstream-http-messages-json
*/
class OptionsBuilder
{
/**
* @internal
*
* @var string
*/
protected $collapseKey;
/**
* @internal
*
* @var string
*/
protected $priority;
/**
* @internal
*
* @var bool
*/
protected $contentAvailable = false;
/**
* @internal
* @var bool
*/
protected $mutableContent;
/**
* @internal
*
* @var bool
*/
protected $delayWhileIdle = false;
/**
* @internal
*
* @var string
*/
protected $timeToLive;
/**
* @internal
*
* @var string
*/
protected $restrictedPackageName;
/**
* @internal
*
* @var bool
*/
protected $dryRun = false;
/**
* This parameter identifies a group of messages
* A maximum of 4 different collapse keys is allowed at any given time.
*
* @param string $collapseKey
*
* @return \LaravelFCM\Message\OptionsBuilder
*/
public function setCollapseKey($collapseKey)
{
$this->collapseKey = $collapseKey;
return $this;
}
/**
* Sets the priority of the message. Valid values are "normal" and "high."
* By default, messages are sent with normal priority.
*
* @param string $priority
*
* @return \LaravelFCM\Message\OptionsBuilder
*
* @throws InvalidOptionsException
* @throws \ReflectionException
*/
public function setPriority($priority)
{
if (!OptionsPriorities::isValid($priority)) {
throw new InvalidOptionsException('priority is not valid, please refer to the documentation or use the constants of the class "OptionsPriorities"');
}
$this->priority = $priority;
return $this;
}
/**
* support only Android and Ios.
*
* An inactive client app is awoken.
* On iOS, use this field to represent content-available in the APNS payload.
* On Android, data messages wake the app by default.
* On Chrome, currently not supported.
*
* @param bool $contentAvailable
*
* @return \LaravelFCM\Message\OptionsBuilder
*/
public function setContentAvailable($contentAvailable)
{
$this->contentAvailable = $contentAvailable;
return $this;
}
/**
* support iOS 10+
*
* When a notification is sent and this is set to true,
* the content of the notification can be modified before it is displayed.
*
* @param String $isMutableContent
* @return OptionsBuilder
*/
public function setMutableContent($isMutableContent)
{
$this->mutableContent = $isMutableContent;
return $this;
}
/**
* When this parameter is set to true, it indicates that the message should not be sent until the device becomes active.
*
* @param bool $delayWhileIdle
*
* @return \LaravelFCM\Message\OptionsBuilder
*/
public function setDelayWhileIdle($delayWhileIdle)
{
$this->delayWhileIdle = $delayWhileIdle;
return $this;
}
/**
* This parameter specifies how long the message should be kept in FCM storage if the device is offline.
*
* @param int $timeToLive (in second) min:0 max:2419200
*
* @return \LaravelFCM\Message\OptionsBuilder
*
* @throws InvalidOptionsException
*/
public function setTimeToLive($timeToLive)
{
if ($timeToLive < 0 || $timeToLive > 2419200) {
throw new InvalidOptionsException("time to live must be between 0 and 2419200, current value is: {$timeToLive}");
}
$this->timeToLive = $timeToLive;
return $this;
}
/**
* This parameter specifies the package name of the application where the registration tokens must match in order to receive the message.
*
* @param string $restrictedPackageName
*
* @return \LaravelFCM\Message\OptionsBuilder
*/
public function setRestrictedPackageName($restrictedPackageName)
{
$this->restrictedPackageName = $restrictedPackageName;
return $this;
}
/**
* This parameter, when set to true, allows developers to test a request without actually sending a message.
* It should only be used for the development.
*
* @param bool $isDryRun
*
* @return \LaravelFCM\Message\OptionsBuilder
*/
public function setDryRun($isDryRun)
{
$this->dryRun = $isDryRun;
return $this;
}
/**
* Get the collapseKey.
*
* @return null|string
*/
public function getCollapseKey()
{
return $this->collapseKey;
}
/**
* Get the priority.
*
* @return null|string
*/
public function getPriority()
{
return $this->priority;
}
/**
* is content available.
*
* @return bool
*/
public function isContentAvailable()
{
return $this->contentAvailable;
}
/**
* is mutable content
*
* @return bool
*/
public function isMutableContent()
{
return $this->mutableContent;
}
/**
* is delay white idle.
*
* @return bool
*/
public function isDelayWhileIdle()
{
return $this->delayWhileIdle;
}
/**
* get time to live.
*
* @return null|int
*/
public function getTimeToLive()
{
return $this->timeToLive;
}
/**
* get restricted package name.
*
* @return null|string
*/
public function getRestrictedPackageName()
{
return $this->restrictedPackageName;
}
/**
* is dry run.
*
* @return bool
*/
public function isDryRun()
{
return $this->dryRun;
}
/**
* build an instance of Options.
*
* @return Options
*/
public function build()
{
return new Options($this);
}
}
/**
* Class OptionsPriorities.
*/
final class OptionsPriorities
{
/**
* @const high priority : iOS, these correspond to APNs priorities 10.
*/
const high = 'high';
/**
* @const normal priority : iOS, these correspond to APNs priorities 5
*/
const normal = 'normal';
/**
* @return array priorities available in fcm
*
* @throws \ReflectionException
*/
public static function getPriorities()
{
$class = new ReflectionClass(__CLASS__);
return $class->getConstants();
}
/**
* check if this priority is supported by fcm.
*
* @param $priority
*
* @return bool
*
* @throws \ReflectionException
*/
public static function isValid($priority)
{
return in_array($priority, static::getPriorities());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace LaravelFCM\Message;
use Illuminate\Contracts\Support\Arrayable;
/**
* Class PayloadData.
*/
class PayloadData implements Arrayable
{
/**
* @internal
*
* @var array
*/
protected $data;
/**
* PayloadData constructor.
*
* @param PayloadDataBuilder $builder
*/
public function __construct(PayloadDataBuilder $builder)
{
$this->data = $builder->getData();
}
/**
* Transform payloadData to array.
*
* @return array
*/
public function toArray()
{
return $this->data;
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace LaravelFCM\Message;
/**
* Class PayloadDataBuilder.
*
* Official google documentation :
*
* @link http://firebase.google.com/docs/cloud-messaging/http-server-ref#downstream-http-messages-json
*/
class PayloadDataBuilder
{
/**
* @internal
*
* @var array
*/
protected $data;
/**
* add data to existing data.
*
* @param array $data
*
* @return PayloadDataBuilder
*/
public function addData(array $data)
{
$this->data = $this->data ?: [];
$this->data = array_merge($data, $this->data);
return $this;
}
/**
* erase data with new data.
*
* @param array $data
*
* @return PayloadDataBuilder
*/
public function setData(array $data)
{
$this->data = $data;
return $this;
}
/**
* Remove all data.
*/
public function removeAllData()
{
$this->data = null;
}
/**
* return data.
*
* @return array
*/
public function getData()
{
return $this->data;
}
/**
* generate a PayloadData.
*
* @return PayloadData new PayloadData instance
*/
public function build()
{
return new PayloadData($this);
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace LaravelFCM\Message;
use Illuminate\Contracts\Support\Arrayable;
/**
* Class PayloadNotification.
*/
class PayloadNotification implements Arrayable
{
/**
* @internal
*
* @var null|string
*/
protected $title;
/**
* @internal
*
* @var null|string
*/
protected $body;
/**
* @internal
*
* @var null/string
*/
protected $channelId;
/**
* @internal
*
* @var null|string
*/
protected $icon;
/**
* @internal
*
* @var null|string
*/
protected $sound;
/**
* @internal
*
* @var null|string
*/
protected $badge;
/**
* @internal
*
* @var null|string
*/
protected $tag;
/**
* @internal
*
* @var null|string
*/
protected $color;
/**
* @internal
*
* @var null|string
*/
protected $clickAction;
/**
* @internal
*
* @var null|string
*/
protected $bodyLocationKey;
/**
* @internal
*
* @var null|string
*/
protected $bodyLocationArgs;
/**
* @internal
*
* @var null|string
*/
protected $titleLocationKey;
/**
* @internal
*
* @var null|string
*/
protected $titleLocationArgs;
/**
* PayloadNotification constructor.
*
* @param PayloadNotificationBuilder $builder
*/
public function __construct(PayloadNotificationBuilder $builder)
{
$this->title = $builder->getTitle();
$this->body = $builder->getBody();
$this->channelId = $builder->getChannelId();
$this->icon = $builder->getIcon();
$this->sound = $builder->getSound();
$this->badge = $builder->getBadge();
$this->tag = $builder->getTag();
$this->color = $builder->getColor();
$this->clickAction = $builder->getClickAction();
$this->bodyLocationKey = $builder->getBodyLocationKey();
$this->bodyLocationArgs = $builder->getBodyLocationArgs();
$this->titleLocationKey = $builder->getTitleLocationKey();
$this->titleLocationArgs = $builder->getTitleLocationArgs();
}
/**
* convert PayloadNotification to array.
*
* @return array
*/
public function toArray()
{
$notification = [
'title' => $this->title,
'body' => $this->body,
'android_channel_id' => $this->channelId,
'icon' => $this->icon,
'sound' => $this->sound,
'badge' => $this->badge,
'tag' => $this->tag,
'color' => $this->color,
'click_action' => $this->clickAction,
'body_loc_key' => $this->bodyLocationKey,
'body_loc_args' => $this->bodyLocationArgs,
'title_loc_key' => $this->titleLocationKey,
'title_loc_args' => $this->titleLocationArgs,
];
// remove null values
$notification = array_filter($notification, function($value) {
return $value !== null;
});
return $notification;
}
}

View File

@@ -0,0 +1,447 @@
<?php
namespace LaravelFCM\Message;
/**
* Class PayloadNotificationBuilder.
*
* Official google documentation :
*
* @link http://firebase.google.com/docs/cloud-messaging/http-server-ref#downstream-http-messages-json
*/
class PayloadNotificationBuilder
{
/**
* @internal
*
* @var null|string
*/
protected $title;
/**
* @internal
*
* @var null|string
*/
protected $body;
/**
* @internal
*
* @var null|string
*/
protected $icon;
/**
* @internal
*
* @var null|string
*/
protected $sound;
/**
* @internal
*
* @var null|string
*/
protected $channelId;
/**
* @internal
*
* @var null|string
*/
protected $badge;
/**
* @internal
*
* @var null|string
*/
protected $tag;
/**
* @internal
*
* @var null|string
*/
protected $color;
/**
* @internal
*
* @var null|string
*/
protected $clickAction;
/**
* @internal
*
* @var null|string
*/
protected $bodyLocationKey;
/**
* @internal
*
* @var null|string
*/
protected $bodyLocationArgs;
/**
* @internal
*
* @var null|string
*/
protected $titleLocationKey;
/**
* @internal
*
* @var null|string
*/
protected $titleLocationArgs;
/**
* Title must be present on android notification and ios (watch) notification.
*
* @param string $title
*/
public function __construct($title = null)
{
$this->title = $title;
}
/**
* Indicates notification title. This field is not visible on iOS phones and tablets.
* but it is required for android.
*
* @param string $title
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Indicates notification body text.
*
* @param string $body
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setBody($body)
{
$this->body = $body;
return $this;
}
/**
* Set a channel ID for android API >= 26.
*
* @param string $channelId
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setChannelId($channelId)
{
$this->channelId = $channelId;
return $this;
}
/**
* Supported Android
* Indicates notification icon. example : Sets value to myicon for drawable resource myicon.
*
* @param string $icon
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setIcon($icon)
{
$this->icon = $icon;
return $this;
}
/**
* Indicates a sound to play when the device receives a notification.
* Supports default or the filename of a sound resource bundled in the app.
*
* @param string $sound
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setSound($sound)
{
$this->sound = $sound;
return $this;
}
/**
* Supported Ios.
*
* Indicates the badge on the client app home icon.
*
* @param string $badge
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setBadge($badge)
{
$this->badge = $badge;
return $this;
}
/**
* Supported Android.
*
* Indicates whether each notification results in a new entry in the notification drawer on Android.
* If not set, each request creates a new notification.
* If set, and a notification with the same tag is already being shown, the new notification replaces the existing one in the notification drawer.
*
* @param string $tag
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setTag($tag)
{
$this->tag = $tag;
return $this;
}
/**
* Supported Android.
*
* Indicates color of the icon, expressed in #rrggbb format
*
* @param string $color
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setColor($color)
{
$this->color = $color;
return $this;
}
/**
* Indicates the action associated with a user click on the notification.
*
* @param string $action
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setClickAction($action)
{
$this->clickAction = $action;
return $this;
}
/**
* Indicates the key to the title string for localization.
*
* @param string $titleKey
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setTitleLocationKey($titleKey)
{
$this->titleLocationKey = $titleKey;
return $this;
}
/**
* Indicates the string value to replace format specifiers in the title string for localization.
*
* @param mixed $titleArgs
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setTitleLocationArgs($titleArgs)
{
$this->titleLocationArgs = $titleArgs;
return $this;
}
/**
* Indicates the key to the body string for localization.
*
* @param string $bodyKey
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setBodyLocationKey($bodyKey)
{
$this->bodyLocationKey = $bodyKey;
return $this;
}
/**
* Indicates the string value to replace format specifiers in the body string for localization.
*
* @param mixed $bodyArgs
*
* @return PayloadNotificationBuilder current instance of the builder
*/
public function setBodyLocationArgs($bodyArgs)
{
$this->bodyLocationArgs = $bodyArgs;
return $this;
}
/**
* Get title.
*
* @return null|string
*/
public function getTitle()
{
return $this->title;
}
/**
* Get body.
*
* @return null|string
*/
public function getBody()
{
return $this->body;
}
/**
* Get channel id for android api >= 26
*
* @return null|string
*/
public function getChannelId()
{
return $this->channelId;
}
/**
* Get Icon.
*
* @return null|string
*/
public function getIcon()
{
return $this->icon;
}
/**
* Get Sound.
*
* @return null|string
*/
public function getSound()
{
return $this->sound;
}
/**
* Get Badge.
*
* @return null|string
*/
public function getBadge()
{
return $this->badge;
}
/**
* Get Tag.
*
* @return null|string
*/
public function getTag()
{
return $this->tag;
}
/**
* Get Color.
*
* @return null|string
*/
public function getColor()
{
return $this->color;
}
/**
* Get ClickAction.
*
* @return null|string
*/
public function getClickAction()
{
return $this->clickAction;
}
/**
* Get BodyLocationKey.
*
* @return null|string
*/
public function getBodyLocationKey()
{
return $this->bodyLocationKey;
}
/**
* Get BodyLocationArgs.
*
* @return null|string|array
*/
public function getBodyLocationArgs()
{
return $this->bodyLocationArgs;
}
/**
* Get TitleLocationKey.
*
* @return string
*/
public function getTitleLocationKey()
{
return $this->titleLocationKey;
}
/**
* GetTitleLocationArgs.
*
* @return null|string|array
*/
public function getTitleLocationArgs()
{
return $this->titleLocationArgs;
}
/**
* Build an PayloadNotification.
*
* @return PayloadNotification
*/
public function build()
{
return new PayloadNotification($this);
}
}

View File

@@ -0,0 +1,226 @@
<?php
namespace LaravelFCM\Message;
use Closure;
use LaravelFCM\Message\Exceptions\NoTopicProvidedException;
/**
* Class Topics.
*
* Create topic or a topic condition
*/
class Topics
{
/**
* @internal
*
* @var array of element in the condition
*/
public $conditions = [];
/**
* Add a topic, this method should be called before any conditional topic.
*
* @param string $first topicName
*
* @return $this
*/
public function topic($first)
{
$this->conditions[] = compact('first');
return $this;
}
/**
* Add a or condition to the precedent topic set.
*
* Parenthesis is a closure
*
* Equivalent of this: **'TopicA' in topic' || 'TopicB' in topics**
*
* ```
* $topic = new Topics();
* $topic->topic('TopicA')
* ->orTopic('TopicB');
* ```
*
* Equivalent of this: **'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)**
*
* ```
* $topic = new Topics();
* $topic->topic('TopicA')
* ->andTopic(function($condition) {
* $condition->topic('TopicB')->orTopic('TopicC');
* });
* ```
*
* > Note: Only two operators per expression are supported by fcm
*
* @param string|Closure $first topicName or closure
*
* @return Topics
*/
public function orTopic($first)
{
return $this->on($first, ' || ');
}
/**
* Add a and condition to the precedent topic set.
*
* Parenthesis is a closure
*
* Equivalent of this: **'TopicA' in topic' && 'TopicB' in topics**
*
* ```
* $topic = new Topics();
* $topic->topic('TopicA')
* ->anTopic('TopicB');
* ```
*
* Equivalent of this: **'TopicA' in topics || ('TopicB' in topics && 'TopicC' in topics)**
*
* ```
* $topic = new Topics();
* $topic->topic('TopicA')
* ->orTopic(function($condition) {
* $condition->topic('TopicB')->AndTopic('TopicC');
* });
* ```
*
* > Note: Only two operators per expression are supported by fcm
*
* @param string|Closure $first topicName or closure
*
* @return Topics
*/
public function andTopic($first)
{
return $this->on($first, ' && ');
}
/**
* @internal
*
* @param $first
* @param $condition
*
* @return $this|Topics
*/
private function on($first, $condition)
{
if ($first instanceof Closure) {
return $this->nest($first, $condition);
}
$this->conditions[] = compact('condition', 'first');
return $this;
}
/**
* @internal
*
* @param Closure $callback
* @param $condition
*
* @return $this
*/
public function nest(Closure $callback, $condition)
{
$topic = new static();
$callback($topic);
if (count($topic->conditions)) {
$open_parenthesis = '(';
$topic = $topic->conditions;
$close_parenthesis = ')';
$this->conditions[] = compact('condition', 'open_parenthesis', 'topic', 'close_parenthesis');
}
return $this;
}
/**
* Transform to array.
*
* @return array|string
*
* @throws NoTopicProvided
*/
public function build()
{
$this->checkIfOneTopicExist();
if ($this->hasOnlyOneTopic()) {
foreach ($this->conditions[0] as $topic) {
return '/topics/'.$topic;
}
}
return [
'condition' => $this->topicsForFcm($this->conditions),
];
}
/**
* @internal
*
* @param $conditions
*
* @return string
*/
private function topicsForFcm($conditions)
{
$condition = '';
foreach ($conditions as $partial) {
if (array_key_exists('condition', $partial)) {
$condition .= $partial['condition'];
}
if (array_key_exists('first', $partial)) {
$topic = $partial['first'];
$condition .= "'$topic' in topics";
}
if (array_key_exists('open_parenthesis', $partial)) {
$condition .= $partial['open_parenthesis'];
}
if (array_key_exists('topic', $partial)) {
$condition .= $this->topicsForFcm($partial['topic']);
}
if (array_key_exists('close_parenthesis', $partial)) {
$condition .= $partial['close_parenthesis'];
}
}
return $condition;
}
/**
* Check if only one topic was set.
*
* @return bool
*/
public function hasOnlyOneTopic()
{
return count($this->conditions) == 1;
}
/**
* @internal
*
* @throws NoTopicProvidedException
*/
private function checkIfOneTopicExist()
{
if (!count($this->conditions)) {
throw new NoTopicProvidedException('At least one topic must be provided');
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace LaravelFCM\Request;
/**
* Class BaseRequest.
*/
abstract class BaseRequest
{
/**
* @internal
*
* @var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* @internal
*
* @var array
*/
protected $config;
/**
* BaseRequest constructor.
*/
public function __construct()
{
$this->config = app('config')->get('fcm.http', []);
}
/**
* Build the header for the request.
*
* @return array
*/
protected function buildRequestHeader()
{
return [
'Authorization' => 'key='.$this->config['server_key'],
'Content-Type' => 'application/json',
'project_id' => $this->config['sender_id'],
];
}
/**
* Build the body of the request.
*
* @return mixed
*/
abstract protected function buildBody();
/**
* Return the request in array form.
*
* @return array
*/
public function build()
{
return [
'headers' => $this->buildRequestHeader(),
'json' => $this->buildBody(),
];
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace LaravelFCM\Request;
/**
* Class GroupRequest.
*/
class GroupRequest extends BaseRequest
{
/**
* @internal
*
* @var string
*/
protected $operation;
/**
* @internal
*
* @var string
*/
protected $notificationKeyName;
/**
* @internal
*
* @var string
*/
protected $notificationKey;
/**
* @internal
*
* @var array
*/
protected $registrationIds;
/**
* GroupRequest constructor.
*
* @param $operation
* @param $notificationKeyName
* @param $notificationKey
* @param $registrationIds
*/
public function __construct($operation, $notificationKeyName, $notificationKey, $registrationIds)
{
parent::__construct();
$this->operation = $operation;
$this->notificationKeyName = $notificationKeyName;
$this->notificationKey = $notificationKey;
$this->registrationIds = $registrationIds;
}
/**
* Build the header for the request.
*
* @return array
*/
protected function buildBody()
{
return [
'operation' => $this->operation,
'notification_key_name' => $this->notificationKeyName,
'notification_key' => $this->notificationKey,
'registration_ids' => $this->registrationIds,
];
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace LaravelFCM\Request;
use LaravelFCM\Message\Topics;
use LaravelFCM\Message\Options;
use LaravelFCM\Message\PayloadData;
use LaravelFCM\Message\PayloadNotification;
/**
* Class Request.
*/
class Request extends BaseRequest
{
/**
* @internal
*
* @var string|array
*/
protected $to;
/**
* @internal
*
* @var Options
*/
protected $options;
/**
* @internal
*
* @var PayloadNotification
*/
protected $notification;
/**
* @internal
*
* @var PayloadData
*/
protected $data;
/**
* @internal
*
* @var Topics|null
*/
protected $topic;
/**
* Request constructor.
*
* @param $to
* @param Options $options
* @param PayloadNotification $notification
* @param PayloadData $data
* @param Topics|null $topic
*/
public function __construct($to, Options $options = null, PayloadNotification $notification = null, PayloadData $data = null, Topics $topic = null)
{
parent::__construct();
$this->to = $to;
$this->options = $options;
$this->notification = $notification;
$this->data = $data;
$this->topic = $topic;
}
/**
* Build the body for the request.
*
* @return array
*/
protected function buildBody()
{
$message = [
'to' => $this->getTo(),
'registration_ids' => $this->getRegistrationIds(),
'notification' => $this->getNotification(),
'data' => $this->getData(),
];
$message = array_merge($message, $this->getOptions());
// remove null entries
return array_filter($message);
}
/**
* get to key transformed.
*
* @return array|null|string
*/
protected function getTo()
{
$to = is_array($this->to) ? null : $this->to;
if ($this->topic && $this->topic->hasOnlyOneTopic()) {
$to = $this->topic->build();
}
return $to;
}
/**
* get registrationIds transformed.
*
* @return array|null
*/
protected function getRegistrationIds()
{
return is_array($this->to) ? $this->to : null;
}
/**
* get Options transformed.
*
* @return array
*/
protected function getOptions()
{
$options = $this->options ? $this->options->toArray() : [];
if ($this->topic && !$this->topic->hasOnlyOneTopic()) {
$options = array_merge($options, $this->topic->build());
}
return $options;
}
/**
* get notification transformed.
*
* @return array|null
*/
protected function getNotification()
{
return $this->notification ? $this->notification->toArray() : null;
}
/**
* get data transformed.
*
* @return array|null
*/
protected function getData()
{
return $this->data ? $this->data->toArray() : null;
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace LaravelFCM\Response;
use Psr\Http\Message\ResponseInterface;
use LaravelFCM\Response\Exceptions\ServerResponseException;
use LaravelFCM\Response\Exceptions\InvalidRequestException;
use LaravelFCM\Response\Exceptions\UnauthorizedRequestException;
/**
* Class BaseResponse.
*/
abstract class BaseResponse
{
const SUCCESS = 'success';
const FAILURE = 'failure';
const ERROR = 'error';
const MESSAGE_ID = 'message_id';
/**
* @var bool
*/
protected $logEnabled = false;
/**
* BaseResponse constructor.
*
* @param \Psr\Http\Message\ResponseInterface $response
*/
public function __construct(ResponseInterface $response)
{
$this->isJsonResponse($response);
$this->logEnabled = app('config')->get('fcm.log_enabled', false);
$responseInJson = json_decode($response->getBody(), true);
$this->parseResponse($responseInJson);
}
/**
* Check if the response given by fcm is parsable.
*
* @param \Psr\Http\Message\ResponseInterface $response
*
* @throws InvalidRequestException
* @throws ServerResponseException
* @throws UnauthorizedRequestException
*/
private function isJsonResponse(ResponseInterface $response)
{
if ($response->getStatusCode() == 200) {
return;
}
if ($response->getStatusCode() == 400) {
throw new InvalidRequestException($response);
}
if ($response->getStatusCode() == 401) {
throw new UnauthorizedRequestException($response);
}
throw new ServerResponseException($response);
}
/**
* parse the response.
*
* @param array $responseInJson
*/
abstract protected function parseResponse($responseInJson);
/**
* Log the response.
*/
abstract protected function logResponse();
}

View File

@@ -0,0 +1,413 @@
<?php
namespace LaravelFCM\Response;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Psr\Http\Message\ResponseInterface;
/**
* Class DownstreamResponse.
*/
class DownstreamResponse extends BaseResponse implements DownstreamResponseContract
{
const MULTICAST_ID = 'multicast_id';
const CANONICAL_IDS = 'canonical_ids';
const RESULTS = 'results';
const MISSING_REGISTRATION = 'MissingRegistration';
const MESSAGE_ID = 'message_id';
const REGISTRATION_ID = 'registration_id';
const NOT_REGISTERED = 'NotRegistered';
const INVALID_REGISTRATION = 'InvalidRegistration';
const UNAVAILABLE = 'Unavailable';
const DEVICE_MESSAGE_RATE_EXCEEDED = 'DeviceMessageRateExceeded';
const INTERNAL_SERVER_ERROR = 'InternalServerError';
/**
* @internal
*
* @var int
*/
protected $numberTokensSuccess = 0;
/**
* @internal
*
* @var int
*/
protected $numberTokensFailure = 0;
/**
* @internal
*
* @var int
*/
protected $numberTokenModify = 0;
/**
* @internal
*
* @var
*/
protected $messageId;
/**
* @internal
*
* @var array
*/
protected $tokensToDelete = [];
/**
* @internal
*
* @var array
*/
protected $tokensToModify = [];
/**
* @internal
*
* @var array
*/
protected $tokensToRetry = [];
/**
* @internal
*
* @var array
*/
protected $tokensWithError = [];
/**
* @internal
*
* @var bool
*/
protected $hasMissingToken = false;
/**
* @internal
*
* @var array
*/
private $tokens;
/**
* DownstreamResponse constructor.
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param $tokens
*/
public function __construct(ResponseInterface $response, $tokens)
{
$this->tokens = is_string($tokens) ? [$tokens] : $tokens;
parent::__construct($response);
}
/**
* Parse the response.
*
* @param $responseInJson
*/
protected function parseResponse($responseInJson)
{
$this->parse($responseInJson);
if ($this->needResultParsing($responseInJson)) {
$this->parseResult($responseInJson);
}
if ($this->logEnabled) {
$this->logResponse();
}
}
/**
* @internal
*
* @param $responseInJson
*/
private function parse($responseInJson)
{
if (array_key_exists(self::MULTICAST_ID, $responseInJson)) {
$this->messageId;
}
if (array_key_exists(self::SUCCESS, $responseInJson)) {
$this->numberTokensSuccess = $responseInJson[self::SUCCESS];
}
if (array_key_exists(self::FAILURE, $responseInJson)) {
$this->numberTokensFailure = $responseInJson[self::FAILURE];
}
if (array_key_exists(self::CANONICAL_IDS, $responseInJson)) {
$this->numberTokenModify = $responseInJson[self::CANONICAL_IDS];
}
}
/**
* @internal
*
* @param $responseInJson
*/
private function parseResult($responseInJson)
{
foreach ($responseInJson[self::RESULTS] as $index => $result) {
if (!$this->isSent($result)) {
if (!$this->needToBeModify($index, $result)) {
if (!$this->needToBeDeleted($index, $result) && !$this->needToResend($index, $result) && !$this->checkMissingToken($result)) {
$this->needToAddError($index, $result);
}
}
}
}
}
/**
* @internal
*
* @param $responseInJson
*
* @return bool
*/
private function needResultParsing($responseInJson)
{
return array_key_exists(self::RESULTS, $responseInJson) && ($this->numberTokensFailure > 0 || $this->numberTokenModify > 0);
}
/**
* @internal
*
* @param $results
*
* @return bool
*/
private function isSent($results)
{
return array_key_exists(self::MESSAGE_ID, $results) && !array_key_exists(self::REGISTRATION_ID, $results);
}
/**
* @internal
*
* @param $index
* @param $result
*
* @return bool
*/
private function needToBeModify($index, $result)
{
if (array_key_exists(self::MESSAGE_ID, $result) && array_key_exists(self::REGISTRATION_ID, $result)) {
if ($this->tokens[$index]) {
$this->tokensToModify[$this->tokens[$index]] = $result[self::REGISTRATION_ID];
}
return true;
}
return false;
}
/**
* @internal
*
* @param $index
* @param $result
*
* @return bool
*/
private function needToBeDeleted($index, $result)
{
if (array_key_exists(self::ERROR, $result) &&
(in_array(self::NOT_REGISTERED, $result) || in_array(self::INVALID_REGISTRATION, $result))) {
if ($this->tokens[$index]) {
$this->tokensToDelete[] = $this->tokens[$index];
}
return true;
}
return false;
}
/**
* @internal
*
* @param $index
* @param $result
*
* @return bool
*/
private function needToResend($index, $result)
{
if (array_key_exists(self::ERROR, $result) && (in_array(self::UNAVAILABLE, $result) || in_array(self::DEVICE_MESSAGE_RATE_EXCEEDED, $result) || in_array(self::INTERNAL_SERVER_ERROR, $result))) {
if ($this->tokens[$index]) {
$this->tokensToRetry[] = $this->tokens[$index];
}
return true;
}
return false;
}
/**
* @internal
*
* @param $result
*
* @return bool
*/
private function checkMissingToken($result)
{
$hasMissingToken = (array_key_exists(self::ERROR, $result) && in_array(self::MISSING_REGISTRATION, $result));
$this->hasMissingToken = (bool) ($this->hasMissingToken | $hasMissingToken);
return $hasMissingToken;
}
/**
* @internal
*
* @param $index
* @param $result
*/
private function needToAddError($index, $result)
{
if (array_key_exists(self::ERROR, $result)) {
if ($this->tokens[$index]) {
$this->tokensWithError[$this->tokens[$index]] = $result[self::ERROR];
}
}
}
/**
* @internal
*/
protected function logResponse()
{
$logger = new Logger('Laravel-FCM');
$logger->pushHandler(new StreamHandler(storage_path('logs/laravel-fcm.log')));
$logMessage = 'notification send to '.count($this->tokens).' devices'.PHP_EOL;
$logMessage .= 'success: '.$this->numberTokensSuccess.PHP_EOL;
$logMessage .= 'failures: '.$this->numberTokensFailure.PHP_EOL;
$logMessage .= 'number of modified token : '.$this->numberTokenModify.PHP_EOL;
$logger->info($logMessage);
}
/**
* Merge two response.
*
* @param DownstreamResponse $response
*/
public function merge(DownstreamResponse $response)
{
$this->numberTokensSuccess += $response->numberSuccess();
$this->numberTokensFailure += $response->numberFailure();
$this->numberTokenModify += $response->numberModification();
$this->tokensToDelete = array_merge($this->tokensToDelete, $response->tokensToDelete());
$this->tokensToModify = array_merge($this->tokensToModify, $response->tokensToModify());
$this->tokensToRetry = array_merge($this->tokensToRetry, $response->tokensToRetry());
$this->tokensWithError = array_merge($this->tokensWithError, $response->tokensWithError());
}
/**
* Get the number of device reached with success.
*
* @return int
*/
public function numberSuccess()
{
return $this->numberTokensSuccess;
}
/**
* Get the number of device which thrown an error.
*
* @return int
*/
public function numberFailure()
{
return $this->numberTokensFailure;
}
/**
* Get the number of device that you need to modify their token.
*
* @return int
*/
public function numberModification()
{
return $this->numberTokenModify;
}
/**
* get token to delete.
*
* remove all tokens returned by this method in your database
*
* @return array
*/
public function tokensToDelete()
{
return $this->tokensToDelete;
}
/**
* get token to modify.
*
* key: oldToken
* value: new token
*
* find the old token in your database and replace it with the new one
*
* @return array
*/
public function tokensToModify()
{
return $this->tokensToModify;
}
/**
* Get tokens that you should resend using exponential backoff.
*
* @return array
*/
public function tokensToRetry()
{
return $this->tokensToRetry;
}
/**
* Get tokens that thrown an error.
*
* key : token
* value : error
*
* In production, remove these tokens from you database
*
* @return array
*/
public function tokensWithError()
{
return $this->tokensWithError;
}
/**
* check if missing tokens was given to the request
* If true, remove all the empty token in your database.
*
* @return bool
*/
public function hasMissingToken()
{
return $this->hasMissingToken;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace LaravelFCM\Response;
/**
* Interface DownstreamResponseContract.
*/
interface DownstreamResponseContract
{
/**
* Merge two response.
*
* @param DownstreamResponse $response
*/
public function merge(DownstreamResponse $response);
/**
* Get the number of device reached with success.
*
* @return int
*/
public function numberSuccess();
/**
* Get the number of device which thrown an error.
*
* @return int
*/
public function numberFailure();
/**
* Get the number of device that you need to modify their token.
*
* @return int
*/
public function numberModification();
/**
* get token to delete.
*
* remove all tokens returned by this method in your database
*
* @return array
*/
public function tokensToDelete();
/**
* get token to modify.
*
* key: oldToken
* value: new token
*
* find the old token in your database and replace it with the new one
*
* @return array
*/
public function tokensToModify();
/**
* Get tokens that you should resend using exponential backoof.
*
* @return array
*/
public function tokensToRetry();
/**
* Get tokens that thrown an error.
*
* key : token
* value : error
*
* In production, remove these tokens from you database
*
* @return array
*/
public function tokensWithError();
/**
* check if missing tokens was given to the request
* If true, remove all the empty token in your database.
*
* @return bool
*/
public function hasMissingToken();
}

View File

@@ -0,0 +1,25 @@
<?php
namespace LaravelFCM\Response\Exceptions;
use Exception;
use Psr\Http\Message\ResponseInterface;
/**
* Class InvalidRequestException.
*/
class InvalidRequestException extends Exception
{
/**
* InvalidRequestException constructor.
*
* @param \Psr\Http\Message\ResponseInterface $response
*/
public function __construct(ResponseInterface $response)
{
$code = $response->getStatusCode();
$responseBody = $response->getBody()->getContents();
parent::__construct($responseBody, $code);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace LaravelFCM\Response\Exceptions;
use Exception;
use Psr\Http\Message\ResponseInterface;
/**
* Class ServerResponseException.
*/
class ServerResponseException extends Exception
{
/**
* retry after.
*
* @var int
*/
public $retryAfter;
/**
* ServerResponseException constructor.
*
* @param \Psr\Http\Message\ResponseInterface $response
*/
public function __construct(ResponseInterface $response)
{
$code = $response->getStatusCode();
$responseHeader = $response->getHeaders();
$responseBody = $response->getBody()->getContents();
if (array_keys($responseHeader, 'Retry-After')) {
$this->retryAfter = $responseHeader['Retry-After'];
}
parent::__construct($responseBody, $code);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace LaravelFCM\Response\Exceptions;
use Exception;
use Psr\Http\Message\ResponseInterface;
/**
* Class UnauthorizedRequestException.
*/
class UnauthorizedRequestException extends Exception
{
/**
* UnauthorizedRequestException constructor.
*
* @param \Psr\Http\Message\ResponseInterface $response
*/
public function __construct(ResponseInterface $response)
{
$code = $response->getStatusCode();
parent::__construct('FCM_SENDER_ID or FCM_SERVER_KEY are invalid', $code);
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace LaravelFCM\Response;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Psr\Http\Message\ResponseInterface;
/**
* Class GroupResponse.
*/
class GroupResponse extends BaseResponse implements GroupResponseContract
{
const FAILED_REGISTRATION_IDS = 'failed_registration_ids';
/**
* @internal
*
* @var int
*/
protected $numberTokensSuccess = 0;
/**
* @internal
*
* @var int
*/
protected $numberTokensFailure = 0;
/**
* @internal
*
* @var array
*/
protected $tokensFailed = [];
/**
* @internal
*
* @var string
*/
protected $to;
/**
* GroupResponse constructor.
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param $to
*/
public function __construct(ResponseInterface $response, $to)
{
$this->to = $to;
parent::__construct($response);
}
/**
* parse the response.
*
* @param $responseInJson
*/
protected function parseResponse($responseInJson)
{
if ($this->parse($responseInJson)) {
$this->parseFailed($responseInJson);
}
if ($this->logEnabled) {
$this->logResponse();
}
}
/**
* Log the response.
*/
protected function logResponse()
{
$logger = new Logger('Laravel-FCM');
$logger->pushHandler(new StreamHandler(storage_path('logs/laravel-fcm.log')));
$logMessage = "notification send to group: $this->to";
$logMessage .= "with $this->numberTokensSuccess success and $this->numberTokensFailure";
$logger->info($logMessage);
}
/**
* @internal
*
* @param $responseInJson
*
* @return bool
*/
private function parse($responseInJson)
{
if (array_key_exists(self::SUCCESS, $responseInJson)) {
$this->numberTokensSuccess = $responseInJson[self::SUCCESS];
}
if (array_key_exists(self::FAILURE, $responseInJson)) {
$this->numberTokensFailure = $responseInJson[self::FAILURE];
}
return $this->numberTokensFailure > 0;
}
/**
* @internal
*
* @param $responseInJson
*/
private function parseFailed($responseInJson)
{
if (array_key_exists(self::FAILED_REGISTRATION_IDS, $responseInJson)) {
foreach ($responseInJson[self::FAILED_REGISTRATION_IDS] as $registrationId) {
$this->tokensFailed[] = $registrationId;
}
}
}
/**
* Get the number of device reached with success.
*
* @return int
*/
public function numberSuccess()
{
return $this->numberTokensSuccess;
}
/**
* Get the number of device which thrown an error.
*
* @return int
*/
public function numberFailure()
{
return $this->numberTokensFailure;
}
/**
* Get all token in group that fcm cannot reach.
*
* @return array
*/
public function tokensFailed()
{
return $this->tokensFailed;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace LaravelFCM\Response;
/**
* Interface GroupResponseContract.
*/
interface GroupResponseContract
{
/**
* Get the number of device reached with success.
*
* @return int
*/
public function numberSuccess();
/**
* Get the number of device which thrown an error.
*
* @return int
*/
public function numberFailure();
/**
* Get all token in group that fcm cannot reach.
*
* @return array
*/
public function tokensFailed();
}

View File

@@ -0,0 +1,151 @@
<?php
namespace LaravelFCM\Response;
use Monolog\Logger;
use LaravelFCM\Message\Topics;
use Monolog\Handler\StreamHandler;
use Psr\Http\Message\ResponseInterface;
/**
* Class TopicResponse.
*/
class TopicResponse extends BaseResponse implements TopicResponseContract
{
const LIMIT_RATE_TOPICS_EXCEEDED = 'TopicsMessageRateExceeded';
/**
* @internal
*
* @var string
*/
protected $topic;
/**
* @internal
*
* @var string
*/
protected $messageId;
/**
* @internal
*
* @var string
*/
protected $error;
/**
* @internal
*
* @var bool
*/
protected $needRetry = false;
/**
* TopicResponse constructor.
*
* @param \Psr\Http\Message\ResponseInterface $response
* @param Topics $topic
*/
public function __construct(ResponseInterface $response, Topics $topic)
{
$this->topic = $topic;
parent::__construct($response);
}
/**
* parse the response.
*
* @param $responseInJson
*/
protected function parseResponse($responseInJson)
{
if (!$this->parseSuccess($responseInJson)) {
$this->parseError($responseInJson);
}
if ($this->logEnabled) {
$this->logResponse();
}
}
/**
* @internal
*
* @param $responseInJson
*/
private function parseSuccess($responseInJson)
{
if (array_key_exists(self::MESSAGE_ID, $responseInJson)) {
$this->messageId = $responseInJson[ self::MESSAGE_ID ];
}
}
/**
* @internal
*
* @param $responseInJson
*/
private function parseError($responseInJson)
{
if (array_key_exists(self::ERROR, $responseInJson)) {
if (in_array(self::LIMIT_RATE_TOPICS_EXCEEDED, $responseInJson)) {
$this->needRetry = true;
}
$this->error = $responseInJson[ self::ERROR ];
}
}
/**
* Log the response.
*/
protected function logResponse()
{
$logger = new Logger('Laravel-FCM');
$logger->pushHandler(new StreamHandler(storage_path('logs/laravel-fcm.log')));
$topic = $this->topic->build();
$logMessage = "notification send to topic: ".json_encode($topic);
if ($this->messageId) {
$logMessage .= "with success (message-id : $this->messageId)";
} else {
$logMessage .= "with error (error : $this->error)";
}
$logger->info($logMessage);
}
/**
* true if topic sent with success.
*
* @return bool
*/
public function isSuccess()
{
return (bool) $this->messageId;
}
/**
* return error message
* you should test if it's necessary to resent it.
*
* @return string error
*/
public function error()
{
return $this->error;
}
/**
* return true if it's necessary resent it using exponential backoff.
*
* @return bool
*/
public function shouldRetry()
{
return $this->needRetry;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace LaravelFCM\Response;
/**
* Interface TopicResponseContract.
*/
interface TopicResponseContract
{
/**
* true if topic sent with success.
*
* @return bool
*/
public function isSuccess();
/**
* return error message
* you should test if it's necessary to resent it.
*
* @return string error
*/
public function error();
/**
* return true if it's necessary resent it using exponential backoff.
*
* @return bool
*/
public function shouldRetry();
}

View File

@@ -0,0 +1,94 @@
<?php
namespace LaravelFCM\Sender;
use LaravelFCM\Request\GroupRequest;
use Psr\Http\Message\ResponseInterface;
/**
* Class FCMGroup.
*/
class FCMGroup extends HTTPSender
{
const CREATE = 'create';
const ADD = 'add';
const REMOVE = 'remove';
/**
* Create a group.
*
* @param $notificationKeyName
* @param array $registrationIds
*
* @return null|string notification_key
*/
public function createGroup($notificationKeyName, array $registrationIds)
{
$request = new GroupRequest(self::CREATE, $notificationKeyName, null, $registrationIds);
$response = $this->client->request('post', $this->url, $request->build());
return $this->getNotificationToken($response);
}
/**
* add registrationId to a existing group.
*
* @param $notificationKeyName
* @param $notificationKey
* @param array $registrationIds registrationIds to add
* @return null|string notification_key
*/
public function addToGroup($notificationKeyName, $notificationKey, array $registrationIds)
{
$request = new GroupRequest(self::ADD, $notificationKeyName, $notificationKey, $registrationIds);
$response = $this->client->request('post', $this->url, $request->build());
return $this->getNotificationToken($response);
}
/**
* remove registrationId to a existing group.
*
* >Note: if you remove all registrationIds the group is automatically deleted
*
* @param $notificationKeyName
* @param $notificationKey
* @param array $registeredIds registrationIds to remove
* @return null|string notification_key
*/
public function removeFromGroup($notificationKeyName, $notificationKey, array $registeredIds)
{
$request = new GroupRequest(self::REMOVE, $notificationKeyName, $notificationKey, $registeredIds);
$response = $this->client->request('post', $this->url, $request->build());
return $this->getNotificationToken($response);
}
/**
* @internal
*
* @param \Psr\Http\Message\ResponseInterface $response
* @return null|string notification_key
*/
private function getNotificationToken(ResponseInterface $response)
{
if (! $this->isValidResponse($response)) {
return null;
}
$json = json_decode($response->getBody()->getContents(), true);
return $json['notification_key'];
}
/**
* @param \Psr\Http\Message\ResponseInterface $response
*
* @return bool
*/
public function isValidResponse(ResponseInterface $response)
{
return $response->getStatusCode() === 200;
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace LaravelFCM\Sender;
use LaravelFCM\Message\Topics;
use LaravelFCM\Request\Request;
use LaravelFCM\Message\Options;
use LaravelFCM\Message\PayloadData;
use LaravelFCM\Response\GroupResponse;
use LaravelFCM\Response\TopicResponse;
use GuzzleHttp\Exception\ClientException;
use LaravelFCM\Response\DownstreamResponse;
use LaravelFCM\Message\PayloadNotification;
/**
* Class FCMSender.
*/
class FCMSender extends HTTPSender
{
const MAX_TOKEN_PER_REQUEST = 1000;
/**
* send a downstream message to.
*
* - a unique device with is registration Token
* - or to multiples devices with an array of registrationIds
*
* @param string|array $to
* @param Options|null $options
* @param PayloadNotification|null $notification
* @param PayloadData|null $data
*
* @return DownstreamResponse|null
*/
public function sendTo($to, Options $options = null, PayloadNotification $notification = null, PayloadData $data = null)
{
$response = null;
if (is_array($to) && !empty($to)) {
$partialTokens = array_chunk($to, self::MAX_TOKEN_PER_REQUEST, false);
foreach ($partialTokens as $tokens) {
$request = new Request($tokens, $options, $notification, $data);
$responseGuzzle = $this->post($request);
$responsePartial = new DownstreamResponse($responseGuzzle, $tokens);
if (!$response) {
$response = $responsePartial;
} else {
$response->merge($responsePartial);
}
}
} else {
$request = new Request($to, $options, $notification, $data);
$responseGuzzle = $this->post($request);
$response = new DownstreamResponse($responseGuzzle, $to);
}
return $response;
}
/**
* Send a message to a group of devices identified with them notification key.
*
* @param $notificationKey
* @param Options|null $options
* @param PayloadNotification|null $notification
* @param PayloadData|null $data
*
* @return GroupResponse
*/
public function sendToGroup($notificationKey, Options $options = null, PayloadNotification $notification = null, PayloadData $data = null)
{
$request = new Request($notificationKey, $options, $notification, $data);
$responseGuzzle = $this->post($request);
return new GroupResponse($responseGuzzle, $notificationKey);
}
/**
* Send message devices registered at a or more topics.
*
* @param Topics $topics
* @param Options|null $options
* @param PayloadNotification|null $notification
* @param PayloadData|null $data
*
* @return TopicResponse
*/
public function sendToTopic(Topics $topics, Options $options = null, PayloadNotification $notification = null, PayloadData $data = null)
{
$request = new Request(null, $options, $notification, $data, $topics);
$responseGuzzle = $this->post($request);
return new TopicResponse($responseGuzzle, $topics);
}
/**
* @internal
*
* @param \LaravelFCM\Request\Request $request
*
* @return null|\Psr\Http\Message\ResponseInterface
*/
protected function post($request)
{
try {
$responseGuzzle = $this->client->request('post', $this->url, $request->build());
} catch (ClientException $e) {
$responseGuzzle = $e->getResponse();
}
return $responseGuzzle;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace LaravelFCM\Sender;
use GuzzleHttp\ClientInterface;
/**
* Class BaseSender.
*/
abstract class HTTPSender
{
/**
* The client used to send messages.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* The URL entry point.
*
* @var string
*/
protected $url;
/**
* Initializes a new sender object.
*
* @param \GuzzleHttp\ClientInterface $client
* @param string $url
*/
public function __construct(ClientInterface $client, $url)
{
$this->client = $client;
$this->url = $url;
}
}

View File

@@ -0,0 +1,460 @@
<?php
use GuzzleHttp\Psr7\Response;
use LaravelFCM\Response\DownstreamResponse;
class DownstreamResponseTest extends FCMTestCase
{
/**
* @test
*/
public function it_construct_a_response_with_a_success()
{
$token = 'new_token';
$response = new Response(200, [], '{
"multicast_id": 108,
"success": 1,
"failure": 0,
"canonical_ids": 0,
"results": [
{ "message_id": "1:08" }
]
}');
$downstreamResponse = new DownstreamResponse($response, $token);
$this->assertEquals(1, $downstreamResponse->numberSuccess());
$this->assertEquals(0, $downstreamResponse->numberFailure());
$this->assertEquals(0, $downstreamResponse->numberModification());
$this->assertCount(0, $downstreamResponse->tokensToDelete());
$this->assertCount(0, $downstreamResponse->tokensToModify());
}
/**
* @test
*/
public function it_construct_a_response_with_multiple_successes()
{
$tokens = [
'first_token',
'second_token',
'third_token',
];
$response = new Response(200, [], '{
"multicast_id": 108,
"success": 3,
"failure": 0,
"canonical_ids": 0,
"results": [
{ "message_id": "1:01" },
{ "message_id": "1:02" },
{ "message_id": "1:03" }
]
}');
$downstreamResponse = new DownstreamResponse($response, $tokens);
$this->assertEquals(3, $downstreamResponse->numberSuccess());
$this->assertEquals(0, $downstreamResponse->numberFailure());
$this->assertEquals(0, $downstreamResponse->numberModification());
$this->assertCount(0, $downstreamResponse->tokensToDelete());
$this->assertCount(0, $downstreamResponse->tokensToModify());
}
/**
* @test
*/
public function it_construct_a_response_with_a_failure()
{
$token = 'new_token';
$response = new Response(200, [], '{
"multicast_id": 108,
"success": 0,
"failure": 1,
"canonical_ids": 0,
"results": [
{ "error": "NotRegistered" }
]
}');
$downstreamResponse = new DownstreamResponse($response, $token);
$this->assertEquals(0, $downstreamResponse->numberSuccess());
$this->assertEquals(1, $downstreamResponse->numberFailure());
$this->assertEquals(0, $downstreamResponse->numberModification());
$this->assertFalse($downstreamResponse->hasMissingToken());
$this->assertCount(1, $downstreamResponse->tokensToDelete());
$this->assertEquals($token, $downstreamResponse->tokensToDelete()[ 0 ]);
$this->assertCount(0, $downstreamResponse->tokensToModify());
}
/**
* @test
*/
public function it_construct_a_response_with_multiple_failures()
{
$tokens = [
'first_token',
'second_token',
'third_token',
'fourth_token',
];
$response = new Response(200, [], '{
"multicast_id": 108,
"success": 0,
"failure": 3,
"canonical_ids": 0,
"results": [
{ "error": "NotRegistered" },
{ "error": "InvalidRegistration" },
{ "error": "NotRegistered" },
{ "error": "MissingRegistration"}
]
}');
$downstreamResponse = new DownstreamResponse($response, $tokens);
$this->assertEquals(0, $downstreamResponse->numberSuccess());
$this->assertEquals(3, $downstreamResponse->numberFailure());
$this->assertEquals(0, $downstreamResponse->numberModification());
$this->assertTrue($downstreamResponse->hasMissingToken());
$this->assertCount(3, $downstreamResponse->tokensToDelete());
$this->assertEquals($tokens[ 0 ], $downstreamResponse->tokensToDelete()[ 0 ]);
$this->assertEquals($tokens[ 1 ], $downstreamResponse->tokensToDelete()[ 1 ]);
$this->assertEquals($tokens[ 2 ], $downstreamResponse->tokensToDelete()[ 2 ]);
$this->assertCount(0, $downstreamResponse->tokensToModify());
}
/**
* @test
*/
public function it_construct_a_response_with_a_token_to_change()
{
$token = 'new_token';
$response = new Response(200, [], '{
"multicast_id": 108,
"success": 0,
"failure": 0,
"canonical_ids": 1,
"results": [
{ "message_id": "1:2342", "registration_id": "32" }
]
}');
$downstreamResponse = new DownstreamResponse($response, $token);
$this->assertEquals(0, $downstreamResponse->numberSuccess());
$this->assertEquals(0, $downstreamResponse->numberFailure());
$this->assertEquals(1, $downstreamResponse->numberModification());
$this->assertCount(0, $downstreamResponse->tokensToDelete());
$this->assertCount(1, $downstreamResponse->tokensToModify());
$this->assertTrue(array_key_exists($token, $downstreamResponse->tokensToModify()));
$this->assertEquals('32', $downstreamResponse->tokensToModify()[ $token ]);
}
/**
* @test
*/
public function it_construct_a_response_with_multiple_tokens_to_change()
{
$tokens = [
'first_token',
'second_token',
'third_token',
];
$response = new Response(200, [], '{
"multicast_id": 108,
"success": 0,
"failure": 0,
"canonical_ids": 3,
"results": [
{ "message_id": "1:2342", "registration_id": "32" },
{ "message_id": "1:2342", "registration_id": "33" },
{ "message_id": "1:2342", "registration_id": "34" }
]
}');
$downstreamResponse = new DownstreamResponse($response, $tokens);
$this->assertEquals(0, $downstreamResponse->numberSuccess());
$this->assertEquals(0, $downstreamResponse->numberFailure());
$this->assertEquals(3, $downstreamResponse->numberModification());
$this->assertCount(0, $downstreamResponse->tokensToDelete());
$this->assertCount(3, $downstreamResponse->tokensToModify());
$this->assertTrue(array_key_exists($tokens[ 0 ], $downstreamResponse->tokensToModify()));
$this->assertEquals('32', $downstreamResponse->tokensToModify()[ $tokens[ 0 ] ]);
$this->assertTrue(array_key_exists($tokens[ 1 ], $downstreamResponse->tokensToModify()));
$this->assertEquals('33', $downstreamResponse->tokensToModify()[ $tokens[ 1 ] ]);
$this->assertTrue(array_key_exists($tokens[ 2 ], $downstreamResponse->tokensToModify()));
$this->assertEquals('34', $downstreamResponse->tokensToModify()[ $tokens[ 2 ] ]);
}
/**
* @test
*/
public function it_construct_a_response_with_a_token_unavailable()
{
$token = 'first_token';
$response = new Response(200, [], '{
"multicast_id": 216,
"success": 0,
"failure": 1,
"canonical_ids": 0,
"results": [
{ "error": "Unavailable" }
]
}');
$downstreamResponse = new DownstreamResponse($response, $token);
$this->assertEquals(0, $downstreamResponse->numberSuccess());
$this->assertEquals(1, $downstreamResponse->numberFailure());
$this->assertEquals(0, $downstreamResponse->numberModification());
// Unavailable is not an error caused by the token validity. it don't need to be deleted$
$this->assertCount(0, $downstreamResponse->tokensToModify());
$this->assertCount(0, $downstreamResponse->tokensToDelete());
$this->assertCount(1, $downstreamResponse->tokensToRetry());
$this->assertEquals($token, $downstreamResponse->tokensToRetry()[0]);
}
/**
* @test
*/
public function it_construct_a_response_with_a_token_server_error()
{
$token = 'first_token';
$response = new Response(200, [], '{
"multicast_id": 216,
"success": 0,
"failure": 1,
"canonical_ids": 0,
"results": [
{ "error": "InternalServerError" }
]
}');
$downstreamResponse = new DownstreamResponse($response, $token);
$this->assertEquals(0, $downstreamResponse->numberSuccess());
$this->assertEquals(1, $downstreamResponse->numberFailure());
$this->assertEquals(0, $downstreamResponse->numberModification());
// Unavailable is not an error caused by the token validity. it don't need to be deleted$
$this->assertCount(0, $downstreamResponse->tokensToModify());
$this->assertCount(0, $downstreamResponse->tokensToDelete());
$this->assertCount(1, $downstreamResponse->tokensToRetry());
$this->assertEquals($token, $downstreamResponse->tokensToRetry()[0]);
}
/**
* @test
*/
public function it_construct_a_response_with_a_token_exceeded()
{
$token = 'first_token';
$response = new Response(200, [], '{
"multicast_id": 216,
"success": 0,
"failure": 1,
"canonical_ids": 0,
"results": [
{ "error": "DeviceMessageRateExceeded" }
]
}');
$downstreamResponse = new DownstreamResponse($response, $token);
$this->assertEquals(0, $downstreamResponse->numberSuccess());
$this->assertEquals(1, $downstreamResponse->numberFailure());
$this->assertEquals(0, $downstreamResponse->numberModification());
// Unavailable is not an error caused by the token validity. it don't need to be deleted$
$this->assertCount(0, $downstreamResponse->tokensToModify());
$this->assertCount(0, $downstreamResponse->tokensToDelete());
$this->assertCount(1, $downstreamResponse->tokensToRetry());
$this->assertEquals($token, $downstreamResponse->tokensToRetry()[0]);
}
/**
* @test
*/
public function it_construct_a_response_with_a_mixed_token_to_retry()
{
$tokens = [
'first_token',
'second_token',
'third_token',
'fourth_token',
'fifth_token',
'sixth_token',
];
$response = new Response(200, [], '{
"multicast_id": 216,
"success": 0,
"failure": 6,
"canonical_ids": 0,
"results": [
{ "error": "DeviceMessageRateExceeded" },
{ "error": "InternalServerError" },
{ "error": "Unavailable" },
{ "error": "DeviceMessageRateExceeded" },
{ "error": "InternalServerError" },
{ "error": "Unavailable" }
]
}');
$downstreamResponse = new DownstreamResponse($response, $tokens);
$this->assertEquals(0, $downstreamResponse->numberSuccess());
$this->assertEquals(6, $downstreamResponse->numberFailure());
$this->assertEquals(0, $downstreamResponse->numberModification());
// Unavailable is not an error caused by the token validity. it don't need to be deleted$
$this->assertCount(0, $downstreamResponse->tokensToModify());
$this->assertCount(0, $downstreamResponse->tokensToDelete());
$this->assertCount(6, $downstreamResponse->tokensToRetry());
$this->assertEquals($tokens[ 0 ], $downstreamResponse->tokensToRetry()[ 0 ]);
$this->assertEquals($tokens[ 1 ], $downstreamResponse->tokensToRetry()[ 1 ]);
$this->assertEquals($tokens[ 2 ], $downstreamResponse->tokensToRetry()[ 2 ]);
$this->assertEquals($tokens[ 3 ], $downstreamResponse->tokensToRetry()[ 3 ]);
$this->assertEquals($tokens[ 4 ], $downstreamResponse->tokensToRetry()[ 4 ]);
$this->assertEquals($tokens[ 5 ], $downstreamResponse->tokensToRetry()[ 5 ]);
}
/**
* @test
*/
public function it_construct_a_response_with_mixed_response()
{
$tokens = [
'first_token',
'second_token',
'third_token',
'fourth_token',
'fifth_token',
'sixth_token',
];
$response = new Response(200, [], '{
"multicast_id": 216,
"success": 3,
"failure": 3,
"canonical_ids": 1,
"results": [
{ "message_id": "1:0408" },
{ "error": "Unavailable" },
{ "error": "InvalidRegistration" },
{ "message_id": "1:1516" },
{ "message_id": "1:2342", "registration_id": "32" },
{ "error": "NotRegistered"}
]
}');
$downstreamResponse = new DownstreamResponse($response, $tokens);
$this->assertEquals(3, $downstreamResponse->numberSuccess());
$this->assertEquals(3, $downstreamResponse->numberFailure());
$this->assertEquals(1, $downstreamResponse->numberModification());
// Unavailable is not an error caused by the token validity. it don't need to be deleted
$this->assertCount(2, $downstreamResponse->tokensToDelete());
$this->assertCount(1, $downstreamResponse->tokensToModify());
$this->assertEquals($tokens[ 2 ], $downstreamResponse->tokensToDelete()[ 0 ]);
$this->assertEquals($tokens[ 5 ], $downstreamResponse->tokensToDelete()[ 1 ]);
$this->assertTrue(array_key_exists($tokens[ 4 ], $downstreamResponse->tokensToModify()));
$this->assertEquals('32', $downstreamResponse->tokensToModify()[ $tokens[ 4 ] ]);
}
/**
* @test
*/
public function it_construct_a_response_with_multiples_response()
{
$tokens = [
'first_token',
'second_token',
'third_token',
'fourth_token',
'fifth_token',
'sixth_token',
'seventh_token',
];
$tokens1 = [
'first_1_token',
'second_1_token',
'third_1_token',
'fourth_1_token',
'fifth_1_token',
'sixth_1_token',
'seventh_1_token',
];
$response = new Response(200, [], '{
"multicast_id": 216,
"success": 3,
"failure": 3,
"canonical_ids": 1,
"results": [
{ "message_id": "1:0408" },
{ "error": "Unavailable" },
{ "error": "InvalidRegistration" },
{ "message_id": "1:1516" },
{ "message_id": "1:2342", "registration_id": "32" },
{ "error": "NotRegistered"},
{ "error": "MessageTooBig"}
]
}');
$downstreamResponse = new DownstreamResponse($response, $tokens);
$downstreamResponse1 = new DownstreamResponse($response, $tokens1);
$downstreamResponse->merge($downstreamResponse1);
$this->assertEquals(6, $downstreamResponse->numberSuccess());
$this->assertEquals(6, $downstreamResponse->numberFailure());
$this->assertEquals(2, $downstreamResponse->numberModification());
// Unavailable is not an error caused by the token validity. it don't need to be deleted
$this->assertCount(4, $downstreamResponse->tokensToDelete());
$this->assertCount(2, $downstreamResponse->tokensToModify());
$this->assertCount(2, $downstreamResponse->tokensWithError());
$this->assertEquals($tokens[ 2 ], $downstreamResponse->tokensToDelete()[ 0 ]);
$this->assertEquals($tokens1[ 2 ], $downstreamResponse->tokensToDelete()[ 2 ]);
$this->assertEquals($tokens[ 5 ], $downstreamResponse->tokensToDelete()[ 1 ]);
$this->assertEquals($tokens1[ 5 ], $downstreamResponse->tokensToDelete()[ 3 ]);
$this->assertCount(2, $downstreamResponse->tokensToRetry());
$this->assertEquals('MessageTooBig', $downstreamResponse->tokensWithError()[$tokens[6]]);
$this->assertEquals('MessageTooBig', $downstreamResponse->tokensWithError()[$tokens1[6]]);
}
}

View File

@@ -0,0 +1,92 @@
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
use LaravelFCM\Sender\FCMSender;
class ResponseTest extends FCMTestCase
{
/**
* @test
*/
public function it_send_a_notification_to_a_device()
{
$response = new Response(200, [], '{
"multicast_id": 216,
"success": 3,
"failure": 3,
"canonical_ids": 1,
"results": [
{ "message_id": "1:0408" }
]
}');
$client = Mockery::mock(Client::class);
$client->shouldReceive('request')->once()->andReturn($response);
$tokens = 'uniqueToken';
$fcm = new FCMSender($client, 'http://test.test');
$fcm->sendTo($tokens);
}
/**
* @test
*/
public function it_send_a_notification_to_more_than_1000_devices()
{
$response = new Response(200, [], '{
"multicast_id": 216,
"success": 3,
"failure": 3,
"canonical_ids": 1,
"results": [
{ "message_id": "1:0408" },
{ "error": "Unavailable" },
{ "error": "InvalidRegistration" },
{ "message_id": "1:1516" },
{ "message_id": "1:2342", "registration_id": "32" },
{ "error": "NotRegistered"}
]
}');
$client = Mockery::mock(Client::class);
$client->shouldReceive('request')->times(10)->andReturn($response);
$tokens = [];
for ($i = 0; $i < 10000; ++$i) {
$tokens[$i] = 'token_'.$i;
}
$fcm = new FCMSender($client, 'http://test.test');
$fcm->sendTo($tokens);
}
/**
* @test
*/
public function an_empty_array_of_tokens_thrown_an_exception()
{
$response = new Response(400, [], '{
"multicast_id": 216,
"success": 3,
"failure": 3,
"canonical_ids": 1,
"results": [
{ "message_id": "1:0408" },
{ "error": "Unavailable" },
{ "error": "InvalidRegistration" },
{ "message_id": "1:1516" },
{ "message_id": "1:2342", "registration_id": "32" },
{ "error": "NotRegistered"}
]
}');
$client = Mockery::mock(Client::class);
$client->shouldReceive('request')->once()->andReturn($response);
$fcm = new FCMSender($client, 'http://test.test');
$this->setExpectedException(\LaravelFCM\Response\Exceptions\InvalidRequestException::class);
$fcm->sendTo([]);
}
}

View File

@@ -0,0 +1,22 @@
<?php
use Illuminate\Foundation\Testing\TestCase;
abstract class FCMTestCase extends TestCase
{
public function createApplication()
{
$app = require __DIR__.'/../vendor/laravel/laravel/bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
$app->register(LaravelFCM\FCMServiceProvider::class);
$app['config']['fcm.driver'] = 'http';
$app['config']['fcm.http.timeout'] = 20;
$app['config']['fcm.http.server_send_url'] = 'http://test.test';
$app['config']['fcm.http.server_key'] = 'key=myKey';
$app['config']['fcm.http.sender_id'] = 'SENDER_ID';
return $app;
}
}

View File

@@ -0,0 +1,72 @@
<?php
use LaravelFCM\Response\GroupResponse;
class GroupResponseTest extends FCMTestCase
{
/**
* @test
*/
public function it_construct_a_response_with_successes()
{
$notificationKey = 'notificationKey';
$response = new \GuzzleHttp\Psr7\Response(200, [], '{
"success": 2,
"failure": 0
}');
$responseGroup = new GroupResponse($response, $notificationKey);
$this->assertEquals(2, $responseGroup->numberSuccess());
$this->assertEquals(0, $responseGroup->numberFailure());
$this->assertCount(0, $responseGroup->tokensFailed());
}
/**
* @test
*/
public function it_construct_a_response_with_failures()
{
$notificationKey = 'notificationKey';
$response = new \GuzzleHttp\Psr7\Response(200, [], '{
"success": 0,
"failure": 2,
"failed_registration_ids":[
"regId1",
"regId2"
]}');
$responseGroup = new GroupResponse($response, $notificationKey);
$this->assertEquals(0, $responseGroup->numberSuccess());
$this->assertEquals(2, $responseGroup->numberFailure());
$this->assertCount(2, $responseGroup->tokensFailed());
$this->assertEquals('regId1', $responseGroup->tokensFailed()[ 0]);
$this->assertEquals('regId2', $responseGroup->tokensFailed()[ 1]);
}
/**
* @test
*/
public function it_construct_a_response_with_partials_failures()
{
$notificationKey = 'notificationKey';
$response = new \GuzzleHttp\Psr7\Response(200, [], '{
"success": 1,
"failure": 2,
"failed_registration_ids":[
"regId1",
"regId2"
]}');
$responseGroup = new GroupResponse($response, $notificationKey);
$this->assertEquals(1, $responseGroup->numberSuccess());
$this->assertEquals(2, $responseGroup->numberFailure());
$this->assertCount(2, $responseGroup->tokensFailed());
}
}

View File

@@ -0,0 +1,143 @@
<?php
use LaravelFCM\Message\Exceptions\InvalidOptionsException;
use LaravelFCM\Message\OptionsBuilder;
use LaravelFCM\Message\OptionsPriorities;
use LaravelFCM\Message\PayloadDataBuilder;
use LaravelFCM\Message\PayloadNotificationBuilder;
class PayloadTest extends FCMTestCase
{
/**
* @test
*/
public function it_construct_a_valid_json_with_option()
{
$targetPartial = '{
"collapse_key":"collapseKey",
"content_available":true
}';
$targetFull = '{
"collapse_key":"collapseKey",
"content_available":true,
"priority":"high",
"delay_while_idle":true,
"time_to_live":200,
"restricted_package_name":"customPackageName",
"dry_run": true
}';
$optionBuilder = new OptionsBuilder();
$optionBuilder->setCollapseKey('collapseKey');
$optionBuilder->setContentAvailable(true);
$json = json_encode($optionBuilder->build()->toArray());
$this->assertJsonStringEqualsJsonString($targetPartial, $json);
$optionBuilder->setPriority(OptionsPriorities::high)
->setDelayWhileIdle(true)
->setDryRun(true)
->setRestrictedPackageName('customPackageName')
->setTimeToLive(200);
$json = json_encode($optionBuilder->build()->toArray());
$this->assertJsonStringEqualsJsonString($targetFull, $json);
}
/**
* @test
*/
public function it_construct_a_valid_json_with_data()
{
$targetAdd = '{
"first_data":"first",
"second_data":true
}';
$targetSet = '
{
"third_data":"third",
"fourth_data":4
}';
$dataBuilder = new PayloadDataBuilder();
$dataBuilder->addData(['first_data' => 'first'])
->addData(['second_data' => true]);
$json = json_encode($dataBuilder->build()->toArray());
$this->assertJsonStringEqualsJsonString($targetAdd, $json);
$dataBuilder->setData(['third_data' => 'third', 'fourth_data' => 4]);
$json = json_encode($dataBuilder->build()->toArray());
$this->assertJsonStringEqualsJsonString($targetSet, $json);
}
/**
* @test
*/
public function it_construct_a_valid_json_with_notification()
{
$targetPartial = '{
"title":"test_title",
"body":"test_body",
"badge":"test_badge",
"sound":"test_sound"
}';
$targetFull = '{
"title":"test_title",
"body":"test_body",
"android_channel_id":"test_channel_id",
"badge":"test_badge",
"sound":"test_sound",
"tag":"test_tag",
"color":"test_color",
"click_action":"test_click_action",
"body_loc_key":"test_body_key",
"body_loc_args":"[ body0, body1 ]",
"title_loc_key":"test_title_key",
"title_loc_args":"[ title0, title1 ]",
"icon":"test_icon"
}';
$notificationBuilder = new PayloadNotificationBuilder();
$notificationBuilder->setTitle('test_title')
->setBody('test_body')
->setSound('test_sound')
->setBadge('test_badge');
$json = json_encode($notificationBuilder->build()->toArray());
$this->assertJsonStringEqualsJsonString($targetPartial, $json);
$notificationBuilder
->setChannelId('test_channel_id')
->setTag('test_tag')
->setColor('test_color')
->setClickAction('test_click_action')
->setBodyLocationKey('test_body_key')
->setBodyLocationArgs('[ body0, body1 ]')
->setTitleLocationKey('test_title_key')
->setTitleLocationArgs('[ title0, title1 ]')
->setIcon('test_icon');
$json = json_encode($notificationBuilder->build()->toArray());
$this->assertJsonStringEqualsJsonString($targetFull, $json);
}
/**
* @test
*/
public function it_throws_an_invalidoptionsexception_if_the_interval_is_too_big()
{
$this->setExpectedException(InvalidOptionsException::class);
$optionBuilder = new OptionsBuilder();
$optionBuilder->setTimeToLive(2419200 * 10);
}
}

View File

@@ -0,0 +1,64 @@
<?php
use GuzzleHttp\Psr7\Response;
use LaravelFCM\Response\TopicResponse;
class TopicsResponseTest extends FCMTestCase
{
/**
* @test
*/
public function it_construct_a_topic_response_with_success()
{
$topic = new \LaravelFCM\Message\Topics();
$topic->topic('topicName');
$response = new Response(200, [], '{
"message_id": "1234"
}');
$topicResponse = new TopicResponse($response, $topic);
$this->assertTrue($topicResponse->isSuccess());
$this->assertFalse($topicResponse->shouldRetry());
$this->assertNull($topicResponse->error());
}
/**
* @test
*/
public function it_construct_a_topic_response_with_error()
{
$topic = new \LaravelFCM\Message\Topics();
$topic->topic('topicName');
$response = new Response(200, [], '{
"error": "MessageTooBig"
}');
$topicResponse = new TopicResponse($response, $topic);
$this->assertFalse($topicResponse->isSuccess());
$this->assertFalse($topicResponse->shouldRetry());
$this->assertEquals('MessageTooBig', $topicResponse->error());
}
/**
* @test
*/
public function it_construct_a_topic_response_with_error_and_it_should_retry()
{
$topic = new \LaravelFCM\Message\Topics();
$topic->topic('topicName');
$response = new Response(200, [], '{
"error": "TopicsMessageRateExceeded"
}');
$topicResponse = new TopicResponse($response, $topic);
$this->assertFalse($topicResponse->isSuccess());
$this->assertTrue($topicResponse->shouldRetry());
$this->assertEquals('TopicsMessageRateExceeded', $topicResponse->error());
}
}

View File

@@ -0,0 +1,149 @@
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
use LaravelFCM\Message\Topics;
use LaravelFCM\Sender\FCMSender;
use LaravelFCM\Message\Exceptions\NoTopicProvidedException;
class TopicsTest extends FCMTestCase
{
/**
* @test
*/
public function it_throw_an_exception_if_no_topic_is_provided()
{
$topics = new Topics();
$this->setExpectedException(NoTopicProvidedException::class);
$topics->build();
}
/**
* @test
*/
public function it_has_only_one_topic()
{
$target = '/topics/myTopic';
$topics = new Topics();
$topics->topic('myTopic');
$this->assertEquals($target, $topics->build());
}
/**
* @test
*/
public function it_has_two_topics_and()
{
$target = [
'condition' => "'firstTopic' in topics && 'secondTopic' in topics",
];
$topics = new Topics();
$topics->topic('firstTopic')->andTopic('secondTopic');
$this->assertEquals($target, $topics->build());
}
/**
* @test
*/
public function it_has_two_topics_or()
{
$target = [
'condition' => "'firstTopic' in topics || 'secondTopic' in topics",
];
$topics = new Topics();
$topics->topic('firstTopic')->orTopic('secondTopic');
$this->assertEquals($target, $topics->build());
}
/**
* @test
*/
public function it_has_two_topics_or_and_one_and()
{
$target = [
'condition' => "'firstTopic' in topics || 'secondTopic' in topics && 'thirdTopic' in topics",
];
$topics = new Topics();
$topics->topic('firstTopic')->orTopic('secondTopic')->andTopic('thirdTopic');
$this->assertEquals($target, $topics->build());
}
/**
* @test
*/
public function it_has_a_complex_topic_condition()
{
$target = [
'condition' => "'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics) || ('TopicD' in topics && 'TopicE' in topics)",
];
$topics = new Topics();
$topics->topic('TopicA')
->andTopic(function ($condition) {
$condition->topic('TopicB')->orTopic('TopicC');
})
->orTopic(function ($condition) {
$condition->topic('TopicD')->andTopic('TopicE');
});
$this->assertEquals($target, $topics->build());
}
/**
* @test
*/
public function it_send_a_notification_to_a_topic()
{
$response = new Response(200, [], '{"message_id":6177433633397011933}');
$client = Mockery::mock(Client::class);
$client->shouldReceive('request')->once()->andReturn($response);
$fcm = new FCMSender($client, 'http://test.test');
$topics = new Topics();
$topics->topic('test');
$response = $fcm->sendToTopic($topics);
$this->assertTrue($response->isSuccess());
$this->assertFalse($response->shouldRetry());
$this->assertNull($response->error());
}
/**
* @test
*/
public function it_send_a_notification_to_a_topic_and_return_error()
{
$response = new Response(200, [], '{"error":"TopicsMessageRateExceeded"}');
$client = Mockery::mock(Client::class);
$client->shouldReceive('request')->once()->andReturn($response);
$fcm = new FCMSender($client, 'http://test.test');
$topics = new Topics();
$topics->topic('test');
$response = $fcm->sendToTopic($topics);
$this->assertFalse($response->isSuccess());
$this->assertTrue($response->shouldRetry());
$this->assertEquals('TopicsMessageRateExceeded', $response->error());
}
}

View File

@@ -0,0 +1,232 @@
<?php
namespace LaravelFCM\Mocks;
use LaravelFCM\Response\DownstreamResponse;
use LaravelFCM\Response\DownstreamResponseContract;
/**
* Class MockDownstreamResponse **Only use it for testing**.
*/
class MockDownstreamResponse implements DownstreamResponseContract
{
/**
* @internal
*
* @var int
*/
protected $numberTokensSuccess = 0;
/**
* @internal
*
* @var
*/
protected $messageId;
/**
* @internal
*
* @var array
*/
protected $tokensToDelete = [];
/**
* @internal
*
* @var array
*/
protected $tokensToModify = [];
/**
* @internal
*
* @var array
*/
protected $tokensToRetry = [];
/**
* @internal
*
* @var array
*/
protected $tokensWithError = [];
/**
* @internal
*
* @var bool
*/
protected $hasMissingToken = false;
/**
* DownstreamResponse constructor.
*
* @param $numberSuccess
*/
public function __construct($numberSuccess)
{
$this->numberTokensSuccess = $numberSuccess;
}
/**
* Not using it.
*
* @param DownstreamResponse $response
*
* @throws \Exception
*/
public function merge(DownstreamResponse $response)
{
throw new \Exception('You cannot use this method for mocking response');
}
/**
* Get the number of device reached with success + numberTokenToModify.
*
* @return int
*/
public function numberSuccess()
{
return $this->numberTokensSuccess + count($this->tokensToModify);
}
/**
* Get the number of device which thrown an error.
*
* @return int
*/
public function numberFailure()
{
return count($this->tokensToDelete()) + count($this->tokensWithError);
}
/**
* Get the number of device that you need to modify their token.
*
* @return int
*/
public function numberModification()
{
return count($this->tokensToModify());
}
/**
* Add a token to delete.
*
* @param $token
* @return MockDownstreamResponse
*/
public function addTokenToDelete($token)
{
$this->tokensToDelete[] = $token;
return $this;
}
/**
* get token to delete
* remove all tokens returned by this method in your database.
*
* @return array
*/
public function tokensToDelete()
{
return $this->tokensToDelete;
}
/**
* Add a token to modify.
*
* @param $oldToken
* @param $newToken
* @return MockDownstreamResponse
*/
public function addTokenToModify($oldToken, $newToken)
{
$this->tokensToModify[$oldToken] = $newToken;
return $this;
}
/**
* get token to modify
* key: oldToken
* value: new token
* find the old token in your database and replace it with the new one.
*
* @return array
*/
public function tokensToModify()
{
return $this->tokensToModify;
}
/**
* Add a token to retry.
*
* @param $token
* @return MockDownstreamResponse
*/
public function addTokenToRetry($token)
{
$this->tokensToRetry[] = $token;
return $this;
}
/**
* Get tokens that you should resend using exponential backoof.
*
* @return array
*/
public function tokensToRetry()
{
return $this->tokensToRetry;
}
/**
* Add a token to errors.
*
* @param $token
* @param $message
* @return MockDownstreamResponse
*/
public function addTokenWithError($token, $message)
{
$this->tokensWithError[$token] = $message;
return $this;
}
/**
* Get tokens that thrown an error
* key : token
* value : error
* In production, remove these tokens from you database.
*
* @return array
*/
public function tokensWithError()
{
return $this->tokensWithError;
}
/**
* change missing token state.
*
* @param $hasMissingToken
* @return MockDownstreamResponse
*/
public function setMissingToken($hasMissingToken)
{
$this->hasMissingToken = $hasMissingToken;
return $this;
}
/**
* check if missing tokens was given to the request
* If true, remove all the empty token in your database.
*
* @return bool
*/
public function hasMissingToken()
{
return $this->hasMissingToken;
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace LaravelFCM\Mocks;
use LaravelFCM\Response\GroupResponseContract;
/**
* Class MockGroupResponse **Only use it for testing**.
*/
class MockGroupResponse implements GroupResponseContract
{
/**
* @internal
*
* @var int
*/
protected $numberTokensSuccess = 0;
/**
* @internal
*
* @var int
*/
protected $numberTokensFailure = 0;
/**
* @internal
*
* @var array
*/
protected $tokensFailed = [];
/**
* @internal
*
* @var string
*/
protected $to;
/**
* set number of success.
*
* @param int $numberSuccess
* @return MockGroupResponse
*/
public function setNumberSuccess($numberSuccess)
{
$this->numberTokensSuccess = $numberSuccess;
return $this;
}
/**
* Get the number of device reached with success.
*
* @return int
*/
public function numberSuccess()
{
return $this->numberTokensSuccess;
}
/**
* set number of failures.
*
* @param $numberFailures
* @return MockGroupResponse
*/
public function setNumberFailure($numberFailures)
{
$this->numberTokensSuccess = $numberFailures;
return $this;
}
/**
* Get the number of device which thrown an error.
*
* @return int
*/
public function numberFailure()
{
return $this->numberTokensFailure;
}
/**
* add a token to the failed list.
*
* @param $tokenFailed
* @return MockGroupResponse
*/
public function addTokenFailed($tokenFailed)
{
$this->tokensFailed[] = $tokenFailed;
return $this;
}
/**
* Get all token in group that fcm cannot reach.
*
* @return array
*/
public function tokensFailed()
{
return $this->tokensFailed;
}
/**
* @return string
*/
public function getTo()
{
return $this->to;
}
/**
* @param string $to
* @return MockGroupResponse
*/
public function setTo($to)
{
$this->to = $to;
return $this;
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace LaravelFCM\Mocks;
use LaravelFCM\Response\TopicResponseContract;
/**
* Class MockTopicResponse **Only use it for testing**.
*/
class MockTopicResponse implements TopicResponseContract
{
/**
* @internal
*
* @var string
*/
protected $topic;
/**
* @internal
*
* @var string
*/
protected $messageId;
/**
* @internal
*
* @var string
*/
protected $error;
/**
* @internal
*
* @var bool
*/
protected $needRetry = false;
/**
* if success set a message id.
*
* @param $messageId
* @return MockTopicResponse
*/
public function setSuccess($messageId)
{
$this->messageId = $messageId;
return $this;
}
/**
* true if topic sent with success.
*
* @return bool
*/
public function isSuccess()
{
return (bool) $this->messageId;
}
/**
* set error.
*
* @param $error
* @return MockTopicResponse
*/
public function setError($error)
{
$this->error = $error;
return $this;
}
/**
* return error message
* you should test if it's necessary to resent it.
*
* @return string error
*/
public function error()
{
return $this->error;
}
/**
* return true if it's necessary resent it using exponential backoff.
*
* @return bool
*/
public function shouldRetry()
{
return (bool) $this->error;
}
}