How to send notifications to Laravel Users with Onesignal notification channel? - reactjs

I'm trying to use laravel-notification-channels/onesignal with Laravel 9, Inertia React project.
For first I setup the client in this way:
useEffect(() => {
OneSignal.init({
appId: "PRIVATE-KEY"
});
}, []);
Testing from Onesignal panel the client is listening.
For the back-end I have created a Notification:
<?php
namespace App\Notifications;
use NotificationChannels\OneSignal\OneSignalChannel;
use NotificationChannels\OneSignal\OneSignalMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\BroadcastMessage;
use App\Models\Order;
class OrderPlacedNotification extends Notification
{
use Queueable;
public $order;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['database', 'broadcast', OneSignalChannel::class];
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
'order' => $this->order,
];
}
public function toBroadcast($notifiable)
{
return new BroadcastMessage([
'order' => $this->order
]);
}
public function toOneSignal($notifiable)
{
return OneSignalMessage::create()
->setSubject("Nuovo ordine!")
->setBody("Vedi l'ordine.")
->setUrl('http://onesignal.com');
}
}
and I send the notification via controller to all users.
All config setted but I can't listen to the user.

I found the solution. In my client I subscibe user to the specific interest and in backend I send notifictions at the users with that specific interest:
Frontend
useEffect(() => {
if(auth.user) {
window.Echo.private(`App.Models.User.${auth.user.id}`).notification(notification => {
console.log(notification);
setNotifications(() => [...notifications, notification]);
})
OneSignal.init(
{
appId: "KEY",
},
//Automatically subscribe to the new_app_version tag
OneSignal.sendTag("orders", "orders", tagsSent => {
// Callback called when tag has finished sending
console.log('TAG SENT', tagsSent);
})
);
}
}, [notifications]);
User Model:
public function routeNotificationForOneSignal()
{
return ['tags' => ['key' => 'orders', 'relation' => '=', 'value' => 'orders']];
}

Related

Laravel 5.8 and React Native using pusher: error listening to private channel: No callbacks on conversations34 for pusher:subscription_succeeded

The problem is when the app developer tries to listen to the event via pusher private channel. Here's the laravel code.
routes/api.php
Route::middleware('auth:api')->post('/broadcast/auth', 'Api\Auth\BroadcastAuthController#auth');
routs/channels.php:
// notifications channel
Broadcast::channel('users.{id}', function ($user, $id) {
return (int)$user->id === (int)$id;
});
// conversations channel
Broadcast::channel('conversations.{conversation}', function ($user, \App\Models\Conversation
$conversation) {
return $conversation->users->contains($user);
});
MessageSent event
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($message)
{
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('conversations.' . $this->message->conversation_id);
}
/**
* Load user relation.
*
* #return array
*/
public function broadcastWith()
{
return [
'message' => $this->message->load('user')
];
}
}
React receiving the message
var Echotest= new Echo({
broadcaster: 'pusher',
key: 'xxxxxxxxxxx',
forceTLS: true,
cluster:"eu",
authEndpoint: 'https://example.com/api/broadcast/auth' ,
auth: {
headers: {
Authorization: "Bearer " + token ,
}
}
});
console.log(Echotest);
Echotest.channel("conversations" + this.state.conversation_id)
.listen("App\Events\MessageSent", e => {
alert(e)
});
But the result is:
Pusher : Event sent : {"event":"pusher:subscribe","data":{"channel":"conversations34"}}
Pusher : Event recd :
{"event":"pusher_internal:subscription_succeeded","channel":"conversations34","data":{}}
Pusher : No callbacks on conversations34 for pusher:subscription_succeeded
Is there anything else need to be done in Laravel to make it work in react? or the issue in react and need to be fixed? Please advise and thanks in advance.
Just Change the "Echotest.channel" to "Echotest.private"

Laravel Lighthouse, how can i get all array data when i do subscription?

I do subscription with laravel + Lighthouse + Laravel WebSockets + vue-apollo tech.
When i subscription, i wanna get all array data, but i only got changed data.
My schema.graphql is below.
type Mutation {
updateTest(id: ID!, name: String, result: Int): Test #update
#broadcast(subscription: "testSub")
}
type Subscription {
testSub(id: ID): Test
}
type Test {
id: ID!
name: String
result: Int
}
This is my vue-apollo code
const subQuery = gql`subscription testSub($id: ID) {
testSub(id:$id) {
id
name
}
}`
const observer = this.$apollo.subscribe({
query: subQuery,
variables () {
return {
id: 14,
}
},
})
observer.subscribe({
next (data) {
console.log('subscribe')
console.log(data)
},
error (error) {
console.log('err')
console.error(error)
},
})
When i do mutation like below.
mutation {
updateTest(id:14, name: "hahaha", result:1) {
id
name
}
}
vue-apollo get subscription like pic.
I recognized return is only changed value instead of all data.
So i change subscription schema like below.
type Subscription {
testSub: [Test] #all
}
I also changed vue-apollo code.
const subQuery = gql`subscription testSub { #delete argument
testSub { #delete argument
id
name
}
}`
const observer = this.$apollo.subscribe({
query: subQuery,
variables () {
return {
id: 14,
}
},
})
observer.subscribe({
next (data) {
console.log('subscribe')
console.log(data)
},
error (error) {
console.log('err')
console.error(error)
},
})
When i do mutation after npm run dev and websocket start, i got this error.
But i already made testSub.
php artisan lighthouse:subscription testSub
This is my testSub file.
<?php
namespace App\GraphQL\Subscriptions;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Subscriptions\Subscriber;
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
use App\Test as Test2;
use App\Events\test;
class TestSub extends GraphQLSubscription
{
/**
* Check if subscriber is allowed to listen to the subscription.
*
* #param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
* #param \Illuminate\Http\Request $request
* #return bool
*/
public function authorize(Subscriber $subscriber, Request $request): bool
{
// TODO implement authorize
return true;
}
/**
* Filter which subscribers should receive the subscription.
*
* #param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
* #param mixed $root
* #return bool
*/
public function filter(Subscriber $subscriber, $root): bool
{
return true;
// TODO implement filter
}
public function encodeTopic(Subscriber $subscriber, string $fieldName): string
{
// Optionally create a unique topic name based on the
// `author` argument.
//$args = $subscriber->args;
//return Str::snake($fieldName).':'.$args['author'];
//return Str::snake($fieldName).':1';
return 'testSub';
}
/**
* Decode topic name.
*
* #param string $fieldName
* #param \App\Post $root
* #return string
*/
public function decodeTopic(string $fieldName, $root): string
{
// Decode the topic name if the `encodeTopic` has been overwritten.
$author_id = $root->author_id;
//return Str::snake($fieldName).':'.$author_id;
return 'testSub';
}
public function resolve($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Test2
{
event(new test());
return $root;
}
}
How can i get all array data instead of changed data?
In your vue-apollo code you have this:
gql`subscription testSub($id: ID) {
testSub(id:$id) {
id
name
}
}`
So, think that it is like a query. Everytime the subscription is fired, you are querying the id and name. If you want to also query the result, just add it:
gql`subscription testSub($id: ID) {
testSub(id:$id) {
id
name
result # <--
}
}`
There is no way to tell to apollo that has to fetch "all" fields of the Test type. You have to explicit those fields.

How can I mock a validation provider in Cakephp

I have a validator that checks if a vat-number is correct. In order to do that it calls an external service. This external call slows the tests down and is unreliable, so I would like to mock it, but I don't understand how I could do it.
public function validationDefault(Validator $validator)
{
$validator->setProvider('vat', 'App\Model\Validation\VatValidation');
$validator->add('vat_no', 'isValidVatNo', [
'rule' => 'validVatNumber',
'provider' => 'vat',
]);
}
And this is the validation provider:
<?php
namespace App\Model\Validation;
use Cake\Core\Configure;
use Cake\Validation\Validation;
use VatNumberCheck\Utility\Model\VatNumberCheck;
class VatValidation extends Validation
{
public static function validVatNumber($check)
{
$vatNumberCheck = new VatNumberCheck();
try {
return $vatNumberCheck->check($check);
} catch (InternalErrorException $e) {
return false;
}
}
}
public function testValidationFail() {
$VatValidator = $this->getMockBuilder('Cake\Validation\Validator')
->setMethods(['validVatNumber'])
->getMock();
$VatValidator->expects($this->any())
->method('validVatNumber')
->will($this->returnValue(false));
$this->Users->getValidator()->setProvider('vat', $VatValidator);
$user = $this->Users->newEntity([
'vat_no' => 'asdf',
]);
$errors = $user->errors();
$this->assertArrayHasKey('vat_no', $errors);
}

Having trouble to set $_SESSION['lang']

Im facing a though nut to crack here... Hope someone out there can help me out !? :-)
Im making a site with slim3, where im trying to setup localization based on this course over at CodeCourse and it seem's that im having troubles to set my session properly, since im using DI-Bridge whereas he is not...
I can't seem to figure out, WHY it fails accordingly to the stacktrace !?
Can anyone help perhaps?
Here's my contaner.php
use function DI\get;
use Interop\Container\ContainerInterface;
use Slim\Csrf\Guard;
use Slim\Flash\Messages;
use Slim\Views\Twig;
use Slim\Views\TwigExtension;
use Illuminate\Translation\Translator;
use Illuminate\Translation\FileLoader;
use Illuminate\Filesystem\Filesystem;
//use Noodlehaus\Config;
use app\views\extensions\TranslationExtension;
use app\handlers\auth\Auth;
use app\handlers\errors\NotFoundHandler;
use app\models\data\About;
use app\models\data\Contact;
use app\models\data\Framework;
use app\models\data\Project;
use app\models\data\Service;
use app\models\data\Site;
use app\models\data\Testimonial;
use app\models\data\User;
use app\validation\contracts\ValidatorInterface;
use app\validation\Validator;
use app\validation\domain\DomainCheck;
use app\validation\email\EmailAvailable;
use app\validation\password\MatchesPassword;
return [
/**
* attaching : XX ->
*/
/* ROUTER */
'router' => get(Slim\Router::class),
/* 404 ERROR */
'notFoundHandler' => function(ContainerInterface $c) {
return new NotFoundHandler ($c->get(Twig::class));
},
/* AUTH */
Auth::class => function (ContainerInterface $c) {
return new Auth;
},
/* CSRF */
Guard::class => function (ContainerInterface $c) {
return new Guard;
},
/* FLASH */
Messages::class => function (ContainerInterface $c) {
return new Messages;
},
/* VALIDATOR */
ValidatorInterface::class => function (ContainerInterface $c) {
return new Validator;
},
/* TRANSLATOR */
Translator::class => function (ContainerInterface $c) {
$fallback = $c->get('settings.translations.fallback');
$loader = new FileLoader(
new Filesystem(), $c->get('settings.translations.path')
);
$translator = new Translator($loader, $_SESSION['lang'] ?? $fallback);
$translator->setFallback($fallback);
return $translator;
},
/* TWIG */
Twig::class => function (ContainerInterface $c) {
$view = new Twig(__DIR__ . '/../resources/views', [
'cache' => false
]);
$view->addExtension(new TwigExtension(
$c->get('router'),
$c->get('request')->getUri()
));
$view->addExtension(new TranslationExtension(
$c->get(Translator::class)
));
$view->getEnvironment()->addGlobal('flash', $c->get(Messages::class));
$view->getEnvironment()->addGlobal('auth', [
'check' => $c->get(Auth::class)->check(),
'user' => $c->get(Auth::class)->user(),
]);
return $view;
},
/**
* attaching : RESPECT -> VALIDATION -> CUSTOM RULES
*/
/* DOMAIN */
DomainCheck::class => function (ContainerInterface $c) {
return new DomainCheck;
},
/* EMAIL */
EmailAvailable::class => function (ContainerInterface $c) {
return new EmailAvailable;
},
/* PASSWORD */
MatchesPassword::class => function (ContainerInterface $c) {
return new MatchesPassword ($c->get(password));
},
/**
* attaching : MODELS ->
*/
/* ABOUT */
About::class => function (ContainerInterface $c) {
return new About;
},
/* CONTACT */
Contact::class => function (ContainerInterface $c) {
return new Contact;
},
/* FRAMEWORK */
Framework::class => function (ContainerInterface $c) {
return new Framework;
},
/* PROJECT */
Project::class => function (ContainerInterface $c) {
return new Project;
},
/* SERVICE */
Service::class => function (ContainerInterface $c) {
return new Service;
},
/* SITE */
Site::class => function (ContainerInterface $c) {
return new Site;
},
/* TESTIMONIAL */
Testimonial::class => function (ContainerInterface $c) {
return new Testimonial;
},
/* USER */
User::class => function (ContainerInterface $c) {
return new User;
},
];
and here's my TranslationController.php
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Router;
class TranslationController {
/**
* #param Request $request
* #param Response $response
* #param Router $router
* #param $args
*
* #return mixed
*/
public function switch(Request $request, Response $response, Router $router, $args) {
if (isset($args['lang'])) {
$_SESSION['lang'] = $args['lang'];
}
return $response->withRedirect($router->pathFor('home'));
}
}
plus the route for translate:
<?php
/**
* adding to view : LOCALIZATION
*/
$app->get('/translate/{lang}', ['app\controllers\TranslationController', 'switch'])->setName('translate.switch');
and finally, here's is the error stacktrace
I don't have much experience with PHP DI, but I would suggest removing the $args argument and get the param via the $request->getAttribute('lang') instead.
<?php
namespace App\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Router;
class TranslationController {
/**
* #param Request $request
* #param Response $response
* #param Router $router
* #param $args
*
* #return mixed
*/
public function switch(Request $request, Response $response, Router $router) {
$_SESSION['lang'] = $request->getAttribute('lang');
return $response->withRedirect($router->pathFor('home'));
}
}
after a litte bit more examination and try/correct, this is what ended up solving it! :
public function switch(Request $request, Response $response, Router $router, $lang) {
if (isset($lang)) {
$_SESSION['lang'] = $lang;
}
return $response->withRedirect($router->pathFor('home'));
}

FOSUserBundle: Success target after password reset according to roles

After the user did reset his password using the password reset of FOSUserBundle, by default he is redirected to the FOSUserProfile. I want to redirect to a different route according to their role. Is this possible and if yes, how?
I do this code but it redirect all kind of users
namespace Acme\UserBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Listener responsible to change the redirection at the end of the password resetting
*/
class PasswordResettingListener implements EventSubscriberInterface {
private $router;
public function __construct(UrlGeneratorInterface $router) {
$this->router = $router;
}
public static function getSubscribedEvents() {
return [
FOSUserEvents::RESETTING_RESET_SUCCESS => 'onPasswordResettingSuccess',
];
}
public function onPasswordResettingSuccess(FormEvent $event) {
$url = $this->router->generate('homepage');
$event->setResponse(new RedirectResponse($url));
}
}
And then I registering it as a service with
services:
acme_user.password_resetting:
class: Acme\UserBundle\EventListener\PasswordResettingListener
arguments: [ "#router" ]
tags:
- { name: kernel.event_subscriber }
Based on your version of Symfony you can choose one of the approaches described in: http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
For example you can use security.authorization_checker service:
Inject it into your service:
services:
acme_user.password_resetting:
class: Acme\UserBundle\EventListener\PasswordResettingListener
arguments: [ "#router", "#security.authorization_checker" ]
tags:
- { name: kernel.event_subscriber }
Then in your actual service:
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
/**
* Listener responsible to change the redirection at the end of the password resetting
*/
class PasswordResettingListener implements EventSubscriberInterface {
private $router;
private $authorizationChecker;
public function __construct(UrlGeneratorInterface $router, AuthorizationChecker $authorizationChecker) {
$this->authorizationChecker = $authorizationChecker;
$this->router = $router;
}
public static function getSubscribedEvents() {
return [
FOSUserEvents::RESETTING_RESET_SUCCESS => 'onPasswordResettingSuccess',
];
}
public function onPasswordResettingSuccess(FormEvent $event) {
//$url = $this->router->generate('homepage');
//$event->setResponse(new RedirectResponse($url));
if (false === $this->authorizationChecker->isGranted('ROLE_ADMIN')) {
// redirect somewhere
} else {
// redirect elsewhere
}
}
}

Resources