Page tree
Skip to end of metadata
Go to start of metadata

Základné pojmy, nastavenia a podpora

Apple developer účet

  • pre registráciu nového websitePushID je nutné mať aktívny Apple developer účet
  • pre firemné účely používame jedno Apple ID - info@comsultia.com
  • nové websitePushID sa vytvára na http://developer.apple.com, v ľavom menu Certificates, Identifiers & Profiles, v podmenu Identifiers > Web Push IDs
  • po vytvorení nového websitePushID je potrebný export certifikátu v P12 formáte (napr. import do Keychainu v MacOS a export)

websitePushID

  • identifikátor webu vo forme web.{doména v reverznom zápise} (napr. web.com.muziker)

Certificates.p12

  • export webPushID certifikátu v P12 formáte
  • potrebný pre vytvorenie webpush balíka

AppleWWDRCA.pem

deviceToken

  • unikátne ID zariadenia/Safari

version

  • verzia push backendu, aktuálne sa používa všade v1, je možné, že do budúcnosti sa to zmení - apple zmení formát requestov, atď.
  • backend v1 by mala byť ideálne restful služba, ale dá sa obsluhovať aj cez rewrite.
  • web push služby sú aktuálne podporované len v OSX, v iOS je to možné len pomocou wrapperu - natívnej aplikácie. Samotné mobilné Safari neobsahuje ani objekt window.safari, ktorý umožňuje web push notifikácie.

push profil používateľa

Vytvorenie webpush balíka pre web

Základom pre Safari pushky je vytvorenie autentifikačného balíka.

Posledný krok vytvorenia balíka je vytvorenie manifestu a bezpečnostného podpisu - je možné na to použiť tento PHP skript (do budúcnosti by sa zišlo prepísať ho do jobu)

Tu je pre istotu funkčný a otestovaný skript aj s intermediate certifikátom:

<?php

// This script creates a valid push package.
// This script assumes that the website.json file and iconset already exist. 
// This script creates a manifest and signature, zips the folder, and returns the push package. 

// Use this script as an example to generate a push package dynamically.


$certificate_path = "Certificates.p12";     // Change this to the path where your certificate is located
$intermediate_certificate_path = "AppleWWDRCA.pem";     // Change this to the path where the intermediate certificate is located
$certificate_password = ""; // Change this to the certificate's import password

// Convenience function that returns an array of raw files needed to construct the package.
function raw_files() {
    return array(
        'icon.iconset/icon_16x16.png',
        'icon.iconset/icon_16x16@2x.png',
        'icon.iconset/icon_32x32.png',
        'icon.iconset/icon_32x32@2x.png',
        'icon.iconset/icon_128x128.png',
        'icon.iconset/icon_128x128@2x.png',
        'website.json'
    );
}

// Copies the raw push package files to $package_dir.
function copy_raw_push_package_files($package_dir) {
    mkdir($package_dir . '/icon.iconset');
    foreach (raw_files() as $raw_file) {
        copy("pushPackage.raw/$raw_file", "$package_dir/$raw_file");
    }
}

// Creates the manifest by calculating the SHA1 hashes for all of the raw files in the package.
function create_manifest($package_dir) {
    // Obtain SHA1 hashes of all the files in the push package
    $manifest_data = array();
    foreach (raw_files() as $raw_file) {
        $manifest_data[$raw_file] = sha1(file_get_contents("$package_dir/$raw_file"));
    }
    file_put_contents("$package_dir/manifest.json", json_encode((object)$manifest_data));
}

// Creates a signature of the manifest using the push notification certificate.
function create_signature($package_dir, $cert_path, $cert_password, $intermediate_certificate_path) {
    // Load the push notification certificate
    
    $pkcs12 = file_get_contents($cert_path);
    $certs = array();
    if(!openssl_pkcs12_read($pkcs12, $certs, $cert_password)) {
        return;
    }
    
    $signature_path = "$package_dir/signature";

    // Sign the manifest.json file with the private key from the certificate
    $cert_data = openssl_x509_read($certs['cert']);
    $private_key = openssl_pkey_get_private($certs['pkey'], $cert_password);
    
    #print "$package_dir/manifest.json<br />";
    #print 'signature_path '.$signature_path."<br />";
    #print 'private_key '.$private_key."<br />";
    #print 'intermediate_certificate_path '.$intermediate_certificate_path."<br />";
    
    openssl_pkcs7_sign("$package_dir/manifest.json", $signature_path, $cert_data, $private_key, array(), PKCS7_BINARY | PKCS7_DETACHED, $intermediate_certificate_path);
    
    // Convert the signature from PEM to DER
    $signature_pem = file_get_contents($signature_path);
    $matches = array();
    if (!preg_match('~Content-Disposition:[^\n]+\s*?([A-Za-z0-9+=/\r\n]+)\s*?-----~', $signature_pem, $matches)) {
        return;
    }
    $signature_der = base64_decode($matches[1]);
    file_put_contents($signature_path, $signature_der);
}

// Zips the directory structure into a push package, and returns the path to the archive.
function package_raw_data($package_dir) {
    $zip_path = "$package_dir.zip";

    // Package files as a zip file
    $zip = new ZipArchive();
    if (!$zip->open("$package_dir.zip", ZIPARCHIVE::CREATE)) {
        error_log('Could not create ' . $zip_path);
        return;
    }

    $raw_files = raw_files();
    $raw_files[] = 'manifest.json';
    $raw_files[] = 'signature';
    foreach ($raw_files as $raw_file) {
        $zip->addFile("$package_dir/$raw_file", $raw_file);
    }

    $zip->close();
    return $zip_path;
}

// Creates the push package, and returns the path to the archive.
function create_push_package() {
    global $certificate_path, $certificate_password, $intermediate_certificate_path;

    // Create a temporary directory in which to assemble the push package
    $package_dir = '/tmp/safariPushPackage' . time();
    if (!mkdir($package_dir)) {
        unlink($package_dir);
        die;
    }

    copy_raw_push_package_files($package_dir);
    create_manifest($package_dir);
    create_signature($package_dir, $certificate_path, $certificate_password, $intermediate_certificate_path);
    $package_path = package_raw_data($package_dir);

    return $package_path;
}


// MAIN
$package_path = create_push_package();
if (empty($package_path)) {
    http_response_code(500);
    die;
}

#header("Content-type: application/zip");
#echo file_get_contents($package_path);
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"package.zip\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($package_path));
ob_end_flush();
@readfile($package_path);
die;


Skript si načítava obsah balíka z podadresára pushPackage.raw, pre korektné vytvorenie balíka sú nutné tieto súbory:

pushPackage.raw/icon.iconset/icon_16x16.png
pushPackage.raw/icon.iconset/icon_16x16@2x.png
pushPackage.raw/icon.iconset/icon_32x32.png
pushPackage.raw/icon.iconset/icon_32x32@2x.png
pushPackage.raw/icon.iconset/icon_128x128.png
pushPackage.raw/icon.iconset/icon_128x128@2x.png
pushPackage.raw/website.json
Certificates.p12
AppleWWDRCA.pem


Vytvorený balík sa umiestňuje do www adresára, aby cesta bola vo forme webServiceURL/version/pushPackages/websitePushID, teda napr.:

https://muziker.sk/v1/pushPackages/web.com.muziker.webpush

Vyžiadanie povolenia v Safari

Pre vyžiadanie povolenia v Safari sa používa jednoduchý skript.

Po povolení Safari volá rovnakú linku ako pri (de)aktivácii v nastaveniach.

Pre naše účely voláme cez XHR "našu" unifikovanú url, zhodnú s backendom pre Firefox/Chrome, napr.:

https://muziker.sk/json/sk/web_push_subscribe

Máme tak možnosť si poslať špecifickejšie údaje pre subscribe.

Aktivácia/Deaktivácia na základe zmeny nastavení notifikácií

Pri zmene/prepínaní nastavena notifikácií pre konkrétny web posiela Safari request na adresu formátu webServiceURL/version/devices/deviceToken/registrations/websitePushID, teda napr.:

https://muziker.sk/v1/devices/ASD89FA...LJS0/registrations/web.com.muziker.webpush

Podľa device tokenu sa vyhľadá profil používateľa.

Ak ide o POST request, zmení sa vo webpush profile subscription_active na 1.

Ak ide o DELETE request, zmení sa vo webpush profile subscription_active na 0.

Overenie podpisu webpush balíka

Pre overenie podpisu vygenerovaného overovacieho webpush balíka sa dá použiť tento postup:

curl -O http://domain.com/v1/pushPackages/web.com.domain
unzip web.com.domain
openssl smime -verify -in signature -inform der -content manifest.json -noverify

Troubleshooting

Safari cez v1 backend loguje chyby v spracovaní cez url webServiceURL/version/log, teda napr https://www.muziker.sk/v1/log. Typ logu je webpush.

Informácie posiela ako array textových správ cez POST request.

Do logu odchádzajú aj informácie o problémoch pri stiahnutí/overení auth balíka, takže treba najprv pripraviť log backend, až potom testovať vygenerovaný balík.

  • No labels