macOS上のSafariでプッシュ通知を作成する方法

最近、Webサービスにプッシュ通知を実装する必要がありました。インターネット上の指示を探して、GCMやFirebaseなどに必要なものをたくさん見つけました。 しかし、Safariブラウザーに関する詳細な手順やステップバイステップの手順は1つだけではありません(macOSでは、Windowsでの動作はわかりませんし、動作しません)。 原則として、SafariのFirebaseは通知する許可を求め、さらには設定にアクセスしましたが、これは目に見えない埃です。 ブラウザがFirebaseからの通知を受け取りたくないことは明らかです。

私はここでこの指示に従ってこれすべて行いましたが、多くの有用なものがありますが、多くのものが欠けているため、常に何かを検索して収集する必要があるため、「macOS上のSafariでプッシュ通知を行う方法」から突然記事を書くことにしました誰が重宝します!



この手順では、Apple Developerアカウントを持っていることを前提としています。 有料のものが必要かどうかはわかりませんが、無料のもの(会社のものを使用しています)で試したことはありません。



Firebaseからのリクエストは次のようになりました






ステップ1:ウェブサイトプッシュIDを登録する



開発者アカウントに入り、 ここで新しいウェブサイトプッシュID 登録します。



ウェブサイトプッシュID










手順2:MacOSのキーチェーンで証明書を要求する



キーチェーンに入り、メニューの「認証アシスタント」を選択します-「認証局からの証明書を要求する」、メール、名前を入力し、「ディスクに保存する」を選択して、ファイルを保存する場所を選択します。



証明書リクエスト






ステップ3:証明書を生成する



開発者のアカウントに戻り、プラス記号をクリックして新しい証明書を作成し、「Website Push ID Certificate」項目を選択し、生成されたWebサイトPush IDを選択し、次に、2番目のステップで生成されたファイルを選択します。すべてが大丈夫です。ファイルをダウンロードできます。 当然、私たちはそれをコンピューターにダウンロードし、それをダブルクリックしてキーチェーンに追加します。



証明書の生成












ステップ4:キーと証明書をエクスポートする



これを初めて行ったとき、私は自分から何が必要かを理解するまで約1時間を費やしましたが、魔法は簡単でした。証明書だけでなく、鍵もエクスポートする必要があります。 これを行うには、「開く」必要があります。次に、キーと証明書の両方をエクスポートし、証明書を右クリックして「エクスポート」を選択し、同じキーを選択します。 すぐに証明書apns-pro-certとキーapns-pro-keyに名前を付けることをお勧めします。これは、次のステップでコマンドを単にコピーし、自分でファイル名を上書きしないようにするためです。 また、エクスポート時にパスワードを指定し、変換中にパスワードを入力する必要があることを覚えておいてください。



鍵と証明書をエクスポートする








ステップ5:証明書をp12に変換する



私はp12からpem形式への証明書の変換を行った非常にクールな指示を見つけました(これは通知の送信とファイルの生成の両方に必要です(さらに詳しく説明します))。 本番フェーズが必要です。 ここでコードを複製します。そうしないと、リンクが示されていて壊れていることが判明した多くの記事に行きます。



Step 1: Create Certificate .pem from Certificate .p12 Command: openssl pkcs12 -clcerts -nokeys -out apns-pro-cert.pem -in apns-pro-cert.p12 Step 2: Create Key .pem from Key .p12 Command : openssl pkcs12 -nocerts -out apns-pro-key.pem -in apns-pro-key.p12 Step 3: Optional (If you want to remove pass phrase asked in second step) Command : openssl rsa -in apns-pro-key.pem -out apns-pro-key-noenc.pem Step 4: Now we have to merge the Key .pem and Certificate .pem to get Production .pem needed for Push Notifications in Production Phase of App Command : cat apns-pro-cert.pem apns-pro-key-noenc.pem > apns-pro.pem (If 3rd step is performed ) Command : cat apns-pro-cert.pem apns-pro-key.pem > apns-pro.pem (if not) Step 5: Check certificate validity and connectivity to APNS Command: openssl s_client -connect gateway.push.apple.com:2195 -cert apns-pro-cert.pem -key apns-pro-key.pem (If 3rd step is not performed ) Command: openssl s_client -connect gateway.push.apple.com:2195 -cert apns-pro-cert.pem -key apns-pro-key-noenc.pem (If performed )
      
      





手順3(パスワードを削除する)を注意深く見てから、手順4と5を注意深く見てください。手順3を実行したかどうかによって異なりますが、私は個人的にはしませんでした。



p12での証明書の変換




ステップ6:証明書をサイトにアップロードする



それらをdokrutフォルダーの上に置いて、Webからアクセスできないようにすることをお勧めします。受信したすべてのファイルのうち、apns-pro.pem-通知を送信し、apns-pro-cert.p12で通知用のパッケージを生成します。さらになります)。



ステップ7:リソースの準備



ブラウザーがサイトからリクエストを受信できるようにするには、アイコン、manifest.json、signature、website.jsonを含むファイルの正しいパッケージを作成する必要があります。通知を初めてリクエストすると、ブラウザーはそれをすべて自分自身にダウンロードしてローカルに保存します。 これを行うには、このような構造を作成し、サイトのルートにすべて配置します。



明確化:website.jsonファイルの「urlFormatString」:「 awery.workreports.pro/#/app/%@ 」、これはユーザーが通知をクリックするリンクです。 %@は、ユーザーがさまざまな種類の通知がさまざまな方法で行われました。



リソースの準備
画像の削減(知りませんでした-クールなリソース: tinypng.comはWebの画像の重量を削減します)







ステップ8:アーカイブを生成する



正しいmanifest.jsonおよび署名ファイルを取得するには、Appleの生成ファイルを使用する必要があります。 私はそれを少し調整し、もう少し余分に削除しましたが、これは正常に機能します。



 <?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 = "../certs/apns-pro-cert.p12"; // Change this to the path where your certificate is located $certificate_password = "PASSPHRASE"; // 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) { // Load the push notification certificate $pkcs12 = file_get_contents($cert_path); $certs = array(); if(!openssl_pkcs12_read($pkcs12, $certs, $cert_password)) { exit('Something wrong with certificate. Err 1'); 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); openssl_pkcs7_sign("$package_dir/manifest.json", $signature_path, $cert_data, $private_key, array(), PKCS7_BINARY | PKCS7_DETACHED); // 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)) { exit('Something wrong with certificate. Err 2'); 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; // Create a temporary directory in which to assemble the push package $package_dir = '/tmp/pushPackage' . 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); $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); die;
      
      





それをドックに入れて呼び出すと、すぐにダウンロードするファイルが与えられます。名前は「pushPackage.zip」である必要があります。保存、解凍し、必要なファイルがすべて作成されたかどうかを確認します(署名)。



ステップ9:v1フォルダーを作成します(またはルーティングを構成します)



必要なファイル、前のステップで生成したアーカイブ、およびリクエストを処理するためのindex.phpファイルを入れるフォルダーv1を作成しました。 サイトがこのようなリクエストに応答する必要があります。



/ v1 / pushPackages / {website}は応答としてアーカイブを提供する必要があります

/ v1 /デバイス/ {device} /登録/ {website}登録および削除(GET / POST-DELETE)

/ v1 /ログエラーのログ



実際、ワーカーを応答としてアーカイブを提供するルートにするだけでよく、残りは多くの処理をせずに単純に200ステータスを提供できますが、必要に応じて(できれば)すべてを実行できます。 ルート/ v1 / devices / {device} / registrations / {website}によって、リクエストがPOST(またはGET)である場合トークンがデバイスに送信され、登録済みIDがWebサイト (私の場合はcom.pro.workreports.awery)に送信されます。デバイス、DELETE要求の場合は削除します。 しかし、JS'aからは別の方法があります。



突然何かがおかしい場合、エラーはroute / v1 / logで発生します 。 これは初めて私を大いに助けてくれました。私はそれを終えて修理していないことを理解しました。



フォルダーV1




ステップ10:JSからトークンをリクエストする



サイトにFirebase(Chrome通知用)とサファリ通知用APNSの両方があります。 これとそれが正しく機能するために、チェックを行います(AngularJSのアプリケーション):



 if ('safari' in window && 'pushNotification' in window.safari) { var permissionData = window.safari.pushNotification.permission('web.com.example.domain'); $scope.checkRemotePermission(permissionData); }else { //FIREBASE HERE }
      
      





およびcheckRemotePermission関数:



 $scope.checkRemotePermission = function (permissionData) { if (permissionData.permission === 'default') { // This is a new web service URL and its validity is unknown. window.safari.pushNotification.requestPermission( 'https://awery.workreports.pro', // The web service URL. 'web.pro.workreports.awery', // The Website Push ID. {}, // Data that you choose to send to your server to help you identify the user. $scope.checkRemotePermission // The callback function. ); } else if (permissionData.permission === 'denied') { if($rootScope.log) console.war('User not allowed notifications'); } else if (permissionData.permission === 'granted') { // The web service URL is a valid push provider, and the user said yes. // permissionData.deviceToken is now available to use. console.log(permissionData.deviceToken, 'YEAH!'); $rootScope.sendTokenToServer(permissionData.deviceToken, 'safari'); } };
      
      





そしてもちろん、 sendTokenToServer関数を説明する意味はありません。トークンとデバイスタイプを入力として受け入れ、データベースに保存します。これを処理できると思います。



権利の要求




ステップ11:バックエンドから通知を送信します。



Safariで通知を送信するためのスクリプトはiOSの場合と同じです。ここに残してください。



 $sql = "SELECT * FROM `user_devices` WHERE `id_user` = ? AND `type` = ?"; $tokens = $dbh->fetchAll($sql, array($id_user, 'safari')); $result = false; if (count($tokens) > 0) { $ctx = stream_context_create(); stream_context_set_option($ctx, 'ssl', 'local_cert', __DIR__ . '/certs/apns-pro.pem'); stream_context_set_option($ctx, 'ssl', 'passphrase', 'PASSPHAREHERE'); // $fp = stream_socket_client( 'ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx); if (!$fp) $log->addError("Failed to connect: $err $errstr"); else { foreach ($tokens as $token) { $deviceToken = $token['token']; // Create the payload body $payload = json_encode(array( 'aps' => array( 'alert' => array( 'title' => $title, 'body' => $message, 'action' => 'Details' ), 'url-args' => array($route!=''?route:'generalUser/notifications/list') ) )); $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload; $result = fwrite($fp, $msg, strlen($msg)); $log->addInfo('Push SAFARI send successfully; result ' . $result . ' Sent message: "' . $payload . '"; ID user: ' . $id_user); } fclose($fp); } }
      
      





ここではすべてが明確です。すべてのユーザートークンを「safari」タイプでキャッチし、すべてのトークンの通知を送信します。



ステップ12:テスト



サイトを立ち上げ、通知を有効にしようとします*確認するか、トークンが保存されます*、送信しようとします。 別のブラウザから送信してみてください。Safariを閉じる必要があります。 すべてが正しく行われた場合-通知が表示されます! 彼らは美しく、Chromeよりもきれいで、ネイティブのもののように見え、より頻繁に来ます FirebaseとSafariの両方で、Safariに通知がある場合がありますが、必ずしもChromeから送信されるとは限りません。



テスト中






突然記事を読んだ後に質問や誤解がある場合-書き込み、可能であればコメントでお答えします。 建設的な批判とコメントを歓迎します。 私は超上級プログラマーのふりをしていないので、どこかで自分が正しくないかもしれないと認めています。 しかし、このアルゴリズムによると、2つのリソースとそれらが機能するすべての場所に通知を行いました。



All Articles