web-push-libs / web-push-php

Web Push library for PHP
MIT License
1.69k stars 295 forks source link

Payload not arriving in chrome #334

Closed musaffar-patel closed 6 months ago

musaffar-patel commented 3 years ago

Hi

It seems the payload arrives as null in event.data on the client side after a push message has been recieved.

Testing with the following code:

require __DIR__ . '/vendor/autoload.php';

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

$auth = [
    'VAPID' => [
        'subject' => 'htts://testpush.test/',
        'publicKey' => 'BBFwt4l1ZvIoCkH2Cp9DSQKGN6MUQ5mFXrT3OLKNsoqSTweloz87hZtIA64Zgx2zROgxPWN1l61q8ezM0QkD2Dg',
        'privateKey' => 'XXXX',
    ],
];

$notifications = [
    [
        'subscription' => Subscription::create([
            'endpoint' => 'https://fcm.googleapis.com/fcm/send/f1azG0YpefY:APA91bEgPUCksrDdHc3vpd8JZUsiLnf6sphqgw8TdyPVyTMaviZGvBY76RPJ2zFmMFpvhxoL3hvbvMSJ_O8Ct5nqKmDYozsZh-IU-gm2DWqeTVRRgW9k6Hq58vxnYi75RehmdXbEr_yb', // Chrome
            'contentEncoding' => 'aes128gcm',
            'publicKey' => 'BBFwt4l1ZvIoCkH2Cp9DSQKGN6MUQ5mFXrT3OLKNsoqSTweloz87hZtIA64Zgx2zROgxPWN1l61q8ezM0QkD2Dg',
            'privateKey' => 'XXXX',
            'authToken' => '',
            'payload' => '{"msg":"Hello World!"}' // optional (defaults null),
        ]),
    ],
];

$webPush = new WebPush($auth);

// send multiple notifications with payload
foreach ($notifications as $notification) {
    $webPush->queueNotification(
        $notification['subscription'],
        $notification['payload'] // optional (defaults null)
    );
}

/**
 * Check sent results
 * @var MessageSentReport $report
 */
foreach ($webPush->flush() as $report) {
    $endpoint = $report->getRequest()->getUri()->__toString();

    if ($report->isSuccess()) {
        echo "[v] Message sent successfully for subscription {$endpoint}.";
    } else {
        echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}";
    }
}

The message is successfully sent, and notification appears on the client, however the event.data is always empty. Couldf you please help with the above?

Musaffar

andrewiankidd commented 3 years ago

Hey, above you have a bug,

your payload should be inside your notification array, not your subscription object


$notifications = [
    [
        'subscription' => Subscription::create([
            'endpoint' => 'https://fcm.googleapis.com/fcm/send/f1azG0YpefY:APA91bEgPUCksrDdHc3vpd8JZUsiLnf6sphqgw8TdyPVyTMaviZGvBY76RPJ2zFmMFpvhxoL3hvbvMSJ_O8Ct5nqKmDYozsZh-IU-gm2DWqeTVRRgW9k6Hq58vxnYi75RehmdXbEr_yb', // Chrome
            'contentEncoding' => 'aes128gcm',
            'publicKey' => 'BBFwt4l1ZvIoCkH2Cp9DSQKGN6MUQ5mFXrT3OLKNsoqSTweloz87hZtIA64Zgx2zROgxPWN1l61q8ezM0QkD2Dg',
            'privateKey' => 'XXXX',
            'authToken' => '',
        ]),
        'payload' => '{"msg":"Hello World!"}' // optional (defaults null),
    ],
];
sadektouati commented 2 years ago

Any solution to this problem? I spent a while trying to make it work. It's not working, "data" field is null

richard-llmnn commented 2 years ago

@sadektouati Did you find a solution or did you give up?

sadektouati commented 2 years ago

@sadektouati Did you find a solution or did you give up?

I sort of gave up. How about you

richard-llmnn commented 2 years ago

@sadektouati Did you find a solution or did you give up?

I sort of gave up. How about you

I am not much further, but I will update / notify you if it works for me.

sadektouati commented 2 years ago

@richard-llmnn Thank you very much

richard-llmnn commented 2 years ago

@sadektouati It works on my localhost :+1: after a bit of try and error. Please make sure that you have the publicKey (p265dh), the endpoint and the authToken (auth). My backend send notification code looks like this:

            $subscription = [
                'endpoint' => $credential->getEndpoint(),
                'publicKey' => $credential->getPublicKey(),
                'authToken' => $credential->getAuthtoken(),
            ];

            $payload = [
                "title" => "Hallo Push-Welt!",
                "body" => "Hi! Dies ist eine Testnachricht!",
                "badge" => "/assets/img/test1.svg",
                "icon" => "/assets/img/test2.svg",
                "image" => "https://unterricht.cloud/wp-content/themes/bueffel/assets/img/unterricht-logo.png",
                "tag" => "test",
                "data" => [
                    "show1" => "/test1",
                    "show2" => "/test2",
                    "default" => "/test3"
                ],
            ];

            $message = [
                'subscription' => Subscription::create($subscription),
                'payload' => json_encode($payload),
            ];
            // $this->webpushConnection is the WebPush object (Minishlink\WebPush\WebPush)
            $this->webpushConnection->queueNotification(
                $message['subscription'],
                $message['payload']
            );

        foreach ($this->webpushConnection->flush() as $report) {

        }
sadektouati commented 2 years ago

@richard-llmnn I'm happy to hear that.

Can you please tell me what goes where ?

Here's the object I get from the subscription: { "keys": {"auth": "string", "p256dh": "string"}, "endpoint": "string", "expirationTime": null }

`$subscription = [

            'endpoint' => $credential->getEndpoint(),

            'publicKey' => $credential->getPublicKey(),

            'authToken' => $credential->getAuthtoken(),

        ];

`

richard-llmnn commented 2 years ago

@sadektouati On the client you get the subscription object. In my project I send the endpoint, keys.auth and keys.p265d to my backend api. The backend saves the data to a mysql database. The structrue looks like:

Webpush_ID | endpoint | publicKey | authToken

1 | https://fcm.googleapis.com/fcm/sen.... | BMrfFtMtL9IWl9vchDbbbYzJlbQwpl... | pbs9Y6r5TscHC64Ce9... // example data

So, my backend saves the data in the database as follows:

Now if you want to send a message to the users that has subscripted to your webpush you have to do the following:

  1. Fetch the data from the database (I use for this symfony/doctrine orm, so all rows are saved in a own object. My name for the row object is $credential and with the getter methods I get the data for the row.)
  2. use the code from my comment: https://github.com/web-push-libs/web-push-php/issues/334#issuecomment-996893924

Some good tutorials are:

sadektouati commented 2 years ago

@richard-llmnn Thank you very much, now it's working. My problem was that intead of this: Subscription::create([ 'endpoint' => $endpoint, 'publicKey' => $p256dh, 'authToken' => $keys_auth, 'contentEncoding' => 'aesgcm', ]), I was doing this: Subscription::create([ 'endpoint' => $endpoint, $p256dh, $keys_auth, 'contentEncoding' => 'aesgcm', ]), I'll open a new issue for this.

Make sure you update the subscription on your server each time it's changed on the client. I did something similar to this: https://medium.com/@madridserginho/how-to-handle-webpush-api-pushsubscriptionchange-event-in-modern-browsers-6e47840d756f

falco442 commented 2 years ago

me too, I cannot send push.. Even if all reports are successful, I cannot see a push notification, neither in the browser on my laptop, nor in the mobile phone (Android). I use Lumen

        $payload = [
            'title' => 'test',
            'body' => 'Messaggio di test',
            'vibrate' => [100, 50, 100],
            'silent' => false,
        ];

        $notifications = Subscription::all()->map(function($item) use ($payload) {
            $subsData = json_decode($item->subscription, true);
            return [
                'subscription' => Subs::create($subsData),
                'payload' => json_encode($payload)
            ];
        })->toArray();

        $auth = Config::get('vapid');
        $webPush = new WebPush($auth);

        // send multiple notifications with payload
        foreach ($notifications as $n) {
            $webPush->queueNotification(
                $n['subscription'],
                $n['payload'] // optional (defaults null)
            );
        }

        $results = [];

        foreach ($webPush->flush() as $report) {
//            $endpoint = $report->getRequest()->getUri()->__toString();

            if (!$report->isSuccess()) {
                $results[] = $report;
            }
        }

where $auth = Config::get('vapid'); returns something like this

[
    'VAPID' => [
        'subject' => 'myemail'
        'publicKey' => '<publicKey>'
        'privateKey' => '<secretKey>'
    ],
]

and $item->subscription is already a json of the form

{
   "endpoint":"endpointUrl",
   "expirationTime":null,
   "keys":{
      "p256dh":"pdkey",
      "auth":"authkey"
   }
}
sadektouati commented 2 years ago

@falco442 in my case it was a matter of making a subscription exactly as Subscription::create([ 'endpoint' => <endpoint>, 'publicKey' => <p256d>, 'authToken' => <keys_auth>, 'contentEncoding' => 'aesgcm', ]),

the endpoint, publicKey, authToken are the keys you received in the subscription object on the client.

richard-llmnn commented 2 years ago

me too, I cannot send push.. Even if all reports are successful, I cannot see a push notification, neither in the browser on my laptop, nor in the mobile phone (Android). I use Lumen

        $payload = [
            'title' => 'test',
            'body' => 'Messaggio di test',
            'vibrate' => [100, 50, 100],
            'silent' => false,
        ];

        $notifications = Subscription::all()->map(function($item) use ($payload) {
            $subsData = json_decode($item->subscription, true);
            return [
                'subscription' => Subs::create($subsData),
                'payload' => json_encode($payload)
            ];
        })->toArray();

        $auth = Config::get('vapid');
        $webPush = new WebPush($auth);

        // send multiple notifications with payload
        foreach ($notifications as $n) {
            $webPush->queueNotification(
                $n['subscription'],
                $n['payload'] // optional (defaults null)
            );
        }

        $results = [];

        foreach ($webPush->flush() as $report) {
//            $endpoint = $report->getRequest()->getUri()->__toString();

            if (!$report->isSuccess()) {
                $results[] = $report;
            }
        }

where $auth = Config::get('vapid'); returns something like this

[
    'VAPID' => [
        'subject' => 'myemail'
        'publicKey' => '<publicKey>'
        'privateKey' => '<secretKey>'
    ],
]

and $item->subscription is already a json of the form

{
   "endpoint":"endpointUrl",
   "expirationTime":null,
   "keys":{
      "p256dh":"pdkey",
      "auth":"authkey"
   }
}

Do you get it working?

sadektouati commented 2 years ago

@richard-llmnn I think the problem is in your $subsData = json_decode($item->subscription, true); it should be : $subsData = [ 'endpoint' => $endpoint, 'publicKey' => $p256dh, 'authToken' => $keys_auth, 'contentEncoding' => 'aesgcm', ]; json_decode($item->subscription, true) would give you [ 'endpoint' => $endpoint, 'keys' => ['p256dh' => $p256dh, 'auth' => $keys_auth]]; I hope this helps you

ankitrox commented 2 years ago

You may have to modify the service worker JS which is listening for push event.

self.addEventListener('push', function (event) {
    if (!(self.Notification && self.Notification.permission === 'granted')) {
        return;
    }

    const sendNotification = body => {
        // you could refresh a notification badge here with postMessage API
        const title = "Web Push example";

        return self.registration.showNotification(title, {
            body,
        });
    };

   // Notice this part carefully. event.waitUntil is only getting called when event.data is non-empty.
    if (event.data) {
        console.log( event );
        const message = event.data.text();
        event.waitUntil(sendNotification(message));
    }
});

Using this script started showing the push notifications.

Alexandre-re-RE commented 2 years ago

Hello,

For me it was one of this key ('endpoint', 'keys', 'p256dh', 'auth', 'contentEncoding', 'publicKey', 'authToken') which was badly written by me, the optional keys in the table are not verified if they are well formatted (so treat as null), to get payload you need to have endpoint, publicKey and authToken OR endpoint, keys with ph256dh and auth.

Hope it save someone times XD