laravel-notification-channels / pusher-push-notifications

Pusher Beams notifications channel for Laravel
http://laravel-notification-channels.com
MIT License
270 stars 67 forks source link

Notifying Users #67

Closed ALGAzevedo closed 2 years ago

ALGAzevedo commented 2 years ago

For the last days i have been trying to use this SDK to notify, for now, only one user that i hardcode the ID when i start the beams instance. Everything goes well when i notify the user id from the debug console on pusher beams.

The problem is, that i try to use this SDK in my notification process that is already sending email notifications, and the workflow goes in the public function toPushNotification($notifiable) but i never get a notification. I followed all the steps and i am only trying for web notification with this simple code:

image

Any help you can give me to make this work?

RhysLees commented 2 years ago

You Notification need to be set like so:

    protected $message;

    public function __construct(string $message) {
        $this->message = $message;
    }

    public function via($notifiable)
    {
        return [PusherChannel::class];
    }

    public function toPushNotification($notifiable)
    {
        return PusherMessage::create()
            ->web()
            ->link($this->url)
            ->title(config('app.name'))
            ->body($this->message);
    }

You can call it by using the notifiable trait $user->notify(new YourNotification($message));

You will need to set your user notification type in your user model with public $pushNotificationType = 'Users';

Then, you need to subscribe your user with beams.

In the head of your application add the following:

<!-- Scripts -->
        <script src="{{ mix('js/app.js') }}" defer></script>

        @auth
        <script>
            var userID = "{!! auth()->user()->id !!}";
        </script>
        @endauth

Then you need to install the npm package npm install @pusher/push-notifications-web In resources\js\bootstrap.js add the following:

import * as PusherPushNotifications from "@pusher/push-notifications-web";

const beamsClient = new PusherPushNotifications.Client({
    instanceId: process.env.MIX_PUSHER_BEAMS_INSTANCE_KEY,
});

const beamsTokenProvider = new PusherPushNotifications.TokenProvider({
    url: process.env.MIX_PUSHER_BEAMS_AUTH_ROUTE,
});

if(typeof userID === 'undefined' || userID === null){
}else{
    var currentUserId = userID;
    var interestFirst = process.env.MIX_PUSHER_BEAMS_INTEREST;
    var interest = interestFirst.concat(currentUserId);

    beamsClient
    .getRegistrationState()
    .then((state) => {
        let states = PusherPushNotifications.RegistrationState;
        switch (state) {
        case states.PERMISSION_DENIED: {
            // Notifications are blocked
            // Show message saying user should unblock notifications in their browser
            console.log('NOTIFICATION PERMISSION DENIED');
            beamsClient.stop();
            break;
        }
        case states.PERMISSION_GRANTED_REGISTERED_WITH_BEAMS: {
            // Ready to receive notifications
            // Show "Disable notifications" button, onclick calls '.stop'
            console.log('NOTIFICATION PERMISSION REGISTERED WITH BEAMS');
            beamsClient
                .getUserId()
                .then((userId) => {
                    // Check if the Beams user matches the user that is currently logged in
                    if (userId !== interest) {
                        // Unregister for notifications
                        console.log('NOTIFICATION PERMISSION UNAUTHORISED');
                        registerBeams();
                    }else{
                        console.log('NOTIFICATION PERMISSION AUTHORISED');
                    }
                })
                .catch(console.error);
            break;
        }
        case states.PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS:
        case states.PERMISSION_PROMPT_REQUIRED: {
            // Need to call start before we're ready to receive notifications
            // Show "Enable notifications" button, onclick calls '.start'
            console.log('NOTIFICATION PERMISSION NOT REGISTED');

            registerBeams();

            break;
        }
        }
    })
    .catch((e) => console.error("COULD NOT GET REGISTRATION STATE", e));
}

window.registerBeams = function () {
    beamsClient
        .start()
        .then(() => beamsClient.setUserId(interest, beamsTokenProvider))
        .then(() => beamsClient.getDeviceId()
            .then((deviceId) =>
                console.log("SUCCESSFULLY REGISTERED WITH BEAMS")
            )
        )
        .then(() => beamsClient.getUserId()
            .then((userId) => {
                // Check if the Beams user matches the user that is currently logged in
                if (userId !== interest) {
                    // Unregister for notifications
                    console.log('NOTIFICATION PERMISSION UNAUTHORISED');
                    return beamsClient.stop();
                }
            })
        )
        .catch(console.error);
}

window.logoutBeams = function () {
    beamsClient.stop()
        .then(() => beamsClient.clearAllState())
        .then(() => console.log('NOTIFICATION STATE HAS BEEN CLEARED'))
        .catch(e => console.error('COULD NOT CLEAR BEAMS STATE', e));
}

Add the following to your .env file:

MIX_PUSHER_BEAMS_INSTANCE_KEY="${PUSHER_BEAMS_INSTANCE_KEY}"
MIX_PUSHER_BEAMS_AUTH_ROUTE="{domainname}/pusher/beams-auth"
MIX_PUSHER_BEAMS_INTEREST="App.Models.User."

In routes\web.php add the following (assumes you are using jetstream/sanctum auth):

Route::middleware(['auth:sanctum', 'verified'])->get('/pusher/beams-auth', function (Request $request) {
        $userID = "App.Models.User." . $request->user()->id;

        $userIDInQueryParam = $request->input('user_id');

        if ($userID !== $userIDInQueryParam) {
            return response('Inconsistent request', 401);
        } else {
            $beamsClient =  new PushNotifications(array(
                "instanceId" => config('services.pusher.beams_instance_id'),
                "secretKey" => config('services.pusher.beams_secret_key')
            ));

            $beamsToken = $beamsClient->generateToken($userID);
            return response()->json($beamsToken);
        }
    })->name('beams.auth');

In the public folder create a file named service-worker.js this has to be named correctly. then inside add importScripts("https://js.pusher.com/beams/service-worker.js");

run npm run dev or npm run prod

then login to your site and check the console. after accepting the notification popup, you should see SUCCESSFULLY REGISTERED WITH BEAMS

IMPORTANT: so you dont use all your subscriptions on beams or get over charged on logout you need to run the logoutBeams js function that we added to bootstrap.js

in my case as i am using jetstream i just had do add logoutBeams(); into the logout buttons in navigation-menu.blade.php

make sure it is the first in the onclick event as it needs to run before the logout occurs

<x-jet-dropdown-link href="{{ route('logout') }}"
            onclick="logoutBeams();
            event.preventDefault();
            this.closest('form').submit();">
    {{ __('Log Out') }}
</x-jet-dropdown-link>

I dont know how janky my method is but i got it working.

Also if you think users would be able to change the userID we set in the head with

@auth
<script>
    var userID = "{!! auth()->user()->id !!}";
</script>
@endauth

don't worry as the route we added compares this value to the request user in the route we made.

hope this helps :)

RhysLees commented 2 years ago

Also the notifications only work for me when i call them from my live/staging site, it dont work local as pusher cant verify a certificate

ALGAzevedo commented 2 years ago

This actually helped a lot! Thank you very much for this :)

And this is actually working in local over https then live since it is still trying to register the user over http with the local domain name, weird thing. I have to check if is there some code that was forgotten from other tries, that does not make much sense, but still.

Once again thank you very much 💯

RhysLees commented 2 years ago

No problem, I was scratching my head for a day and a half trying to figure it out, uploaded it to my staging server and it worked straight away. I wish there was a better way to get the user I'd into js but it seems to do the trick.

Once again though remember to unset the user subscription on logout otherwise it will cost you on pusher beams.

If you need anything, please reach out 😁

ALGAzevedo commented 2 years ago

Yes i am doing all of that :) The only problem now is that when iI run npm run prod to push to the server, since I have, for now, 2 different instances, i get conflicts on the app.js , service-worker.js, bootstrap.js, mix-manifest.json. I think its because of different ${APP_URL} in both instances and somehow when we run the npm run prod it generates the files that wont match the ones already in the server. He doesnt deploy and it keeps the app.js file with the http://LOCAL_DOMAIN/pusher/beams-auth. I managed to make it work changing the files directly in the server but it doesnt make much sense to this approach on the long term.

Anyway, you gave me enough help!

RhysLees commented 2 years ago

Hey jsut a quick update, in the newest version of laravel you can now pass in the auth user id using the new Js helper

It ensures that the data passes is properly escaped

AwesomeKuro commented 2 years ago

Hey jsut a quick update, in the newest version of laravel you can now pass in the auth user id using the new Js helper

It ensures that the data passes is properly escaped

I followed the whole method but the PUT request returns 401 unauthorized, invalid JWT signature, and the url ends in /user Any idea of what could have been wrong?