When OSX Mavericks was released, I was excited to see that push notifications were supported within Safari. So now, just like iOS, we can send push notifications to Safari regardless of whether the browser is open or not. This post gives overview of how to get them working from PHP using the PHP APNS library.

Registering with Apple and Generating Certificates

This post assumes you have read this article from Apple and have already generated your security certificates.

PHP APNS Library

The PHP APNS (Apple Push Notification Service) library makes it extremely easy to get Safari push notifications working on your website. Just install it with composer to get started:

Installation

composer require jwage/php-apns
composer install

Push Package

Once you have the package installed you can start integrating it with your website. The first thing we need to do is create a base push package for your website. From Apple’s website:

When a user is asked for permission to receive push notifications, Safari asks your web server for a package. The package contains data that is used by the notification UI, such as your website name and icon, as well as a cryptographic signature. The signature verifies that your notification hasn’t been intercepted by a man-in-the-middle attack and that it is indeed coming from a trusted source: you.

You can find a sample base push package within the PHP APNS library here. Copy this somewhere in your application and customize the icons in the icon.iconset folder and the website.json:

{
    "websiteName": "WebsiteName",
    "websitePushID": "web.com.domain",
    "allowedDomains": ["http://{{ host }}", "https://{{ host }}"],
    "urlFormatString": "http://{{ host }}/%@",
    "authenticationToken": "{{ userId }}",
    "webServiceURL": "https://{{ host }}/safari_push_notifications/{{ userId }}"
}

Web Service Endpoints

Now it is time to setup some endpoints in your web application for Safari to communicate with. You need endpoints to do the following:

  • Generate a push package for an individual user.
  • Register a users device token.
  • Deregister a users device token.
  • Record log data sent by Safari when errors occur.

Here is a pseudo controller demonstrating what each endpoint needs to do:

<?php

namespace App\Controller;

use JWage\APNS\Certificate;
use JWage\APNS\Safari\PackageGenerator;

class SafariPushNotificationsController
{
    public function packageAction($userId)
    {
        // Send push notification package to browser when Safari asks user for permission to send you notifications.

        $certificate = new Certificate(file_get_contents('apns.p12'), 'certpassword');
        $packageGenerator = new PackageGenerator(
            $certificate, '/path/to/base/pushPackage/path', 'yourhost.com'
        );

        // returns JWage\APNS\Safari\Package instance
        $package = $packageGenerator->createPushPackageForUser('userid');

        // send $package->getZipPath() to the browser
    }

    public function registerAction($userId, $deviceToken)
    {
        // store $deviceToken on the $userId so you can use it later to send pushes
    }

    public function deregisterAction($userId, $deviceToken)
    {
        // remove $deviceToken from $userId
    }

    public function logAction($userId)
    {
        // log information sent for debugging purposes
    }
}

Requesting Permission

Requesting permission to send a user notifications in Safari can be done using this little snippet of javascript on your website:

if ('safari' in window && 'pushNotification' in window.safari) {
    var checkRemotePermission = function (permissionData) {
        if (permissionData.permission === 'default') {
            window.safari.pushNotification.requestPermission(
                'http://domain.com/safari_push_notifications/{{ userId }}',
                'web.com.domain',
                {
                    'userId': {{ userId }}
                },
                checkRemotePermission
            );
        } else if (permissionData.permission === 'denied') {
            // do something when permission is denied
        } else if (permissionData.permission === 'granted') {
            // do something when permission is granted
        }
    };

    // Ensure that the user can receive Safari Push Notifications.
    var permissionData = window.safari.pushNotification.permission('web.com.domain');
    checkRemotePermission(permissionData);
}

When a user visits your website with Safari in OSX Mavericks it will hit your web services endpoint, download the push package and ask the user if they want to receive notifications from your website.

Sending Push Notifications

Once you have done all of the above, you should be ready to send push notifications. The PHP APNS library makes this extremely easy. Here is an example:

use JWage\APNS\Certificate;
use JWage\APNS\Client;
use JWage\APNS\Sender;
use JWage\APNS\SocketClient;

$certificate = new Certificate(file_get_contents('apns.pem'));
$socketClient = new SocketClient($certificate, 'gateway.push.apple.com', 2195);
$client = new Client($socketClient);
$sender = new Sender($client);

$sender->send('devicetoken', 'Title of push', 'Body of push', 'http://deeplink.com');

You can create an easy to use service in your application for sending push notifications to an instance of User, assuming it implements a getSafariDeviceToken() method:

class SafariPushNotificationSender
{
    private $sender;

    public function __construct(Sender $sender)
    {
        $this->sender = $sender;
    }

    public function sendToUser(User $user, $title, $body, $link)
    {
        return $this->sender->send($user->getSafariDeviceToken(), $title, $body, $link);
    }
}

Now it is as simple as the following:

$safariPushNotificationSender = new SafariPushNotificationSender($sender);
$safariPushNotificationSender->sendToUser($user, 'Title of push', 'Body of push', 'http://deeplink.com');

I hope this was helpful! If you have any questions please leave them in the comments. Enjoy!