PHPとJavaScriptだけでPush通知を送信できるようにする

Push通知ってなんかハードルが高いイメージの方、多いと思います。
僕もその認識でした。Firebaseとかなんとか使うんかなーって。
実際はものすごく簡単です。PHPとJavaScriptだけで完結します。
ということで、やっていきましょう。
目次
記事の内容
この記事では、Webサイト上でのPush通知をservice worker、PHPのライブラリを用いて作成していきます。
あくまでPush通知を行うことにフォーカスを向けるため、解説を省略する部分もありますが、ご了承ください。
※Service Worker、PWAの知識とPHPのComposerの知識(composerでライブラリをインストールできる程度)がないと厳しいかもしれません。読み進めて分からないところはどんどん調べてください。
フォルダ構成

icon.pngは正方形のアイコン画像を用意しておいてください。
サイズは128×128、またはそれ以上です。
ファイルの内容を記載していくので、コピペして作成していってください。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<title>WebPushテスト</title>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<!-- アドレスバー等のブラウザのUIを非表示 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- default(Safariと同じ) / black(黒) / black-translucent(ステータスバーをコンテンツに含める) -->
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- ホーム画面に表示されるアプリ名 -->
<meta name="apple-mobile-web-app-title" content="WebPush Test">
<!-- ホーム画面に表示されるアプリアイコン -->
<link rel="apple-touch-icon" href="icon.png">
<!-- ウェブアプリマニフェスト -->
<link rel="manifest" href="manifest.json">
<script defer src='service-worker.js'></script>
<script src='webpush.js'></script>
</head>
<body>
<a href="javascript:allowWebPush()">WebPushを許可する</a>
</body>
</html>
manifest.json
scope、starturlに関しては各自で指定してください。
{
"name": "WebPush Test",
"short_name": "WebPush Test",
"theme_color": "#3c76ff",
"background_color": "#0011d3",
"display": "standalone",
"scope": "/pushtest",
"start_url": "/pushtest/"
}
service-worker.js
function base64Decode(text,charset){
return fetch(`data:text/plain;charset=${charset};base64,`+text).then(response=>response.text());
}
// プッシュ通知を受け取ったときのイベント
self.addEventListener('push', async function (event) {
//serverからのメッセージ
var msg=event.data.text();
msg=await base64Decode(msg);
msg=msg.split('!|!');
const title = msg[0];
const options = {
body: msg[1], // メッセージ
tag: msg[2], // 通知固有のタグ(このプログラムではURLの伝達に使用)
icon: 'icon.png', // アイコン
badge: 'icon.png' // アイコン(縮小版)
};
event.waitUntil(self.registration.showNotification(title, options));
});
// プッシュ通知のクリックイベント
self.addEventListener('notificationclick', function (event) {
var notification_url=event.notification.tag;//通知に関連付けられているURL
event.notification.close();
event.waitUntil(
// プッシュ通知をクリックしたときに開くURL
clients.openWindow(notification_url)
);
});
// Service Worker インストール時に実行
self.addEventListener('install', (event) => {
console.log('service worker install ...');
});
※PHPのSend.php、JSのwebpush.jsについては下記手順の後に作成していきます。
公開鍵/秘密鍵の作成
Push通知を送信するには、公開鍵と秘密鍵が必要です。
自前で用意するのは難しいので、下のサイトで生成してくれます。
そうしたら、そこに記載されている生成されたPublic KeyとPrivate Keyのセットをメモしておきます。

一文字も抜けないように気を付けてコピーしてください。
webpush.js
//サービスワーカーを登録
self.addEventListener('load', async () => {
if ('serviceWorker' in navigator) {
window.sw = await navigator.serviceWorker.register('/raspberrypi_s/pushtest/service-worker.js', {scope: '/raspberrypi_s/pushtest/'});
}
});
//Push通知を許可する仕組み
async function allowWebPush() {
if ('Notification' in window) {
let permission = Notification.permission;
if (permission === 'denied') {
alert('Push通知が拒否されているようです。ブラウザの設定からPush通知を有効化してください');
return false;
} else if (permission === 'granted') {
alert('すでにWebPushを許可済みです');
return false;
}
}
// 取得したPublicKey
const appServerKey = 'ここにPublicKeyを入力';
const applicationServerKey = urlB64ToUint8Array(appServerKey);
// push managerにサーバーキーを渡し、トークンを取得
let subscription = undefined;
try {
subscription = await window.sw.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey
});
} catch (e) {
alert('Push通知機能が拒否されたか、エラーが発生しましたので、Push通知は送信されません。');
return false;
}
// 必要なトークンを変換して取得
const key = subscription.getKey('p256dh');
const token = subscription.getKey('auth');
const request = {
endpoint: subscription.endpoint,
userPublicKey: btoa(String.fromCharCode.apply(null, new Uint8Array(key))),
userAuthToken: btoa(String.fromCharCode.apply(null, new Uint8Array(token)))
};
console.log(request);
}
//トークンを変換するプログラム
function urlB64ToUint8Array (base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
中央付近にあるconst appServerKey = 'ここにPublicKeyを入力';
の部分を先ほど取得したPublic Keyに置き換えてください。
このような感じです。(実際のPublic Keyではありません。)
const appServerKey = 'BBB8jP1FFFbeg56CDpQQQXf28CCCVkJKKKQnhwwrh8OwDuY2mkQtDDDxv8QC91Ozpn8gPPPDFXPYMpH71HYnAAA';
ユーザーの認証情報を取得
先ほど作成したファイルをすべてサーバーにアップロードできたら、index.htmlを開きます。
「WebPushを許可する」のリンクをクリックすると、以下画像のように許可が求められるのので、「許可」をクリックします。

ここでデベロッパーツールを開いてください。Ctrl+Shift+I
で開くことができます。

コンソールにendpoint、userAuthToken、userPublicKeyが表示されているので、コピーします。
ライブラリのインストール
web-pushというライブラリを使用します。
Composerからインストールしてください。
composer require minishlink/web-push
インストールする上での注意
SSH等でインストールと思いますが、PHPバージョンが7.4などの最新バージョンでないと、最新のリリースがインストールできません。
php -v
とコマンドを打って、バージョンが古い場合は更新しておいてください。
更新(バージョン変更)の仕方はサーバー・レンタルサーバー会社等によって異なるので、調べてみてください。
送信するPHPファイルの作成
Send.phpを作成してください。
先ほどよりもPublic Keyなどの置き換えするところがかなり多いので、入力するところ、順番等ミスがないように注意をして作成してください。
Send.php
<?php
require_once 'vendor/autoload.php';
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
if(!isset($_POST["n_title"])){exit("error");}
if(!isset($_POST["n_body"])){exit("error");}
if(!isset($_POST["n_url"])){exit("error");}
const VAPID_SUBJECT = 'サイトのドメイン(例:https://example.org)';
const PUBLIC_KEY = 'サイトで取得したPublic Key(公開鍵)';
const PRIVATE_KEY = 'サイトで取得したPrivate Key(秘密鍵)';
// push通知認証用のデータ
$subscription = Subscription::create([
'endpoint' => 'consoleに表示されたendpoint',
'publicKey' => 'consoleに表示されたpublickey',
'authToken' => 'consoleに表示されたauthToken',
]);
// ブラウザに認証させる
$auth = [
'VAPID' => [
'subject' => VAPID_SUBJECT,
'publicKey' => PUBLIC_KEY,
'privateKey' => PRIVATE_KEY,
]
];
$webPush = new WebPush($auth);
$body_msg=base64_encode($_POST["n_title"].'!|!'.$_POST["n_body"].'!|!'.$_POST["n_url"]);
$report = $webPush->sendOneNotification(
$subscription,
$body_msg
);
$endpoint = $report->getRequest()->getUri()->__toString();
if ($report->isSuccess()) {
echo '送信成功しました';
} else {
echo '送信失敗です';
}
これで完成です!Send.phpにPOSTでアクセスしてみてください。
パラーメーターは以下の通りです。
n_title : 通知のタイトル
n_body : 通知のコンテンツ(テキスト)
n_url : 通知をクリックした際のURL
Jquery Ajaxでの例
$.ajax("https://example.org/pushtest/Send.php",{
type:"POST",
data:{
n_title:"タイトル",
n_body:"コンテンツだよ",
n_url:"https://example.org/notifaction_page"
}
}).done((res)=>{
if(res==="送信成功しました"){
alert("送信成功!");
}else{
alert("送信失敗!");
}
});
実際にユーザーに配信できるようにするには
途中ブラウザのデベロッパーツールから情報を取り出したと思います。
この情報を、Ajaxを用いてサーバーに送信し、データベースに保存しておき、送信するときにデータベースから先ほど置き換えた箇所に変数で入れ込むことで、ユーザーに個別に配信することができます。
また、カスタマイズ次第では通知に表示されるアイコンを変えることなども可能です。
通知が送信できない・エラーが出る場合
「送信失敗です」と表示される場合
ユーザーの認証データが古いもの(期限切れ)である可能性が高いです。
認証データを再生成してそのデータで試してみてください。
ブラウザのサイトデータを削除した後などに期限切れになる可能性があるので、期限切れを検知して更新できるプログラムも作るべきといえます。今回の記事では触れませんが。
PHPのエラーが発生する場合
- Public Key等の入力位置を間違えている・文字が抜けている
正しくない値を入れるだけでエラーになってしまうので注意してください。 - ライブラリ・PHPのバージョンが古い
ライブラリやPHPのバージョンは新しいものでないと動かない場合があります。バージョンの確認を行ってください。 - Composerの場所を正しく指定していない
Send.phpの一行目にcomposerを読み込んでいますが、実際にサーバーにインストールされているものを読み込むようにファイル指定を行っているか確認してください。 - 「error」のレスポンスが返ってくる場合
POST送信で、n_title,n_body,n_urlをすべて送信していないとエラーになります。
参考
以下のサイトを参考にさせていただきました。
サイト: https://zenn.dev/nnahito/articles/fd2c8b0ad0d19a