JSとPHPでWebPushを送信するWebアプリケーションを作ってみる
はじめましての方ははじめまして。
またお前かな方はこんにちは。
Nな人(えぬなひと)と申します。
普段はQiitaや自前ブログ「Nな人のWeb示録」で記事を投稿しておりますが、
今回はノリでZennに記事を書いていこうと思います。
では、やっていきましょうか。
目的
service workerなるものを使えば、ブラウザでPush通知が使える!(iPhone以外)
アプリケーションではなく、Webの技術で作れるってすごい!先進的!(iPhone以外)
ということで、ServiceWorkerで個人的に一番やってみたかった、WebPushを実装していこうと思います。
これでアプリを作らなくてもPush通知が送れるようになりますね!(iPhone以外)
とても便利な世界になってきました。(iPhone以外)
お断り
ServiceWorkerには
「manifest.json」や「service-worker.js」
を用意する必要があります。
ですがこやつらはググれば一発で出てくるので、詳しくは書きません。
コードは載せますが、言及しません。
あしからず。
WebPushの流れ
- ブラウザでWebPushを利用していいかユーザに尋ねる
- 公開鍵と秘密鍵を使ってサーバーキーを作成
- push managerという仕組みにサーバーキーを渡してトークンを取得
- 取得されたトークンを変換(ようわからん)する
- 変換したトークンを使ってWebサーバからPush通知を送信!
以上
WebPushの仕組みを作っていく
事前準備
今回するコードです。
ファイルリストはこんな感じ。
ひとまず、以下のファイルをコピペで準備してください。
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="WebPusher">
<!-- ホーム画面に表示されるアプリアイコン -->
<link rel="apple-touch-icon" href="icon-152x152.png">
<!-- ウェブアプリマニフェストの読み込み -->
<link rel="manifest" href="manifest.json">
<link rel='icon' type='image/png' href='favicon.png'>
<script defer src='service-worker.js'></script>
<script src='webpush.js'></script>
</head>
<body>
<a href="javascript:allowWebPush()">WebPushを許可する</a>
</body>
</html>
manifest.json
{
"name": "WebPusher",
"short_name": "WebPusher",
"theme_color": "#44518d",
"background_color": "#2e3659",
"display": "standalone",
"scope": "/",
"start_url": "/"
}
service-worker.js
// プッシュ通知を受け取ったときのイベント
self.addEventListener('push', function (event) {
const title = 'Push通知テスト';
const options = {
body: event.data.text(), // サーバーからのメッセージ
tag: title, // タイトル
icon: 'icon-512x512.png', // アイコン
badge: 'icon-512x512.png' // アイコン
};
event.waitUntil(self.registration.showNotification(title, options));
});
// プッシュ通知をクリックしたときのイベント
self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(
// プッシュ通知をクリックしたときにブラウザを起動して表示するURL
clients.openWindow('https://nnahito.com/')
);
});
// Service Worker インストール時に実行される
// キャッシュするファイルとかをここで登録する
self.addEventListener('install', (event) => {
console.log('service worker install ...');
});
公開鍵、秘密鍵の用意
鍵の文字数に制限があるようです。
以下のサイト様で発行してくれます。
Public Key
とPrivate Key
の値をコピーしましょう。
WebPushを許可してもらうコードの作成
以下を丸コピで作成してください。
ただし、27行目のconst appServerKey = 'ここに取得したPublicKeyを書く';
の部分は、
先ほど用意した公開鍵(Public Key)を当てはめてください。
また、43行目付近の「// 必要なトークンを変換して取得(これが重要!!!)
」は後で使います。
そのことだけ念頭に置いておいてください。
webpush.jsの作成
/**
* サービスワーカーの登録
*/
self.addEventListener('load', async () => {
if ('serviceWorker' in navigator) {
window.sw = await navigator.serviceWorker.register('/service-worker.js', {scope: '/'});
}
});
/**
* WebPushを許可する仕組み
*/
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);
}
/**
* トークンを変換するときに使うロジック
* @param {*} base64String
*/
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;
}
これでフロント側の準備は整いました。
ここでPHPのサーバを立てて、一度ブラウザでJSを実行してみます。
今まで作成したコードのファイルが有るディレクトリまでcdし、
以下のコマンドを叩いてください。
php -S localhost:8080
ブラウザで localhost:8080 にアクセスし、「WebPushを許可する」リンクをクリックしてください。
Push通知許可の後、ブラウザコンソールに情報が表示されると思います。
次のステップではこの情報を利用していきます。
Push通知を送信する(PHP)
以下のライブラリを使うと簡単に実装できます。
composer require minishlink/web-push
SendPush.phpの作成
以下の項目を埋めて、コードを完成させてください。
-
VAPID_SUBJECT
- あなたのWebサイトのURL(http://localhost:5000/ とか https://nnahito.com/ とか)
-
PUBLIC_KEY
- 公開鍵( https://web-push-codelab.glitch.me/ で取得したやつ )
-
PRIVATE_KEY
- 秘密鍵( https://web-push-codelab.glitch.me/ で取得したやつ )
-
endpoint
- webpush.jsの「必要なトークンを変換して取得(これが重要!!!)」で取得されたendpoint
-
publicKey
- webpush.jsの「必要なトークンを変換して取得(これが重要!!!)」で取得されたuserPublicKey
-
authToken
- webpush.jsの「必要なトークンを変換して取得(これが重要!!!)」で取得されたuserAuthToken
<?php
require_once 'vendor/autoload.php';
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
const VAPID_SUBJECT = 'ここにあなたのWebサイトのURL(http://localhost:5000/ とか https://nnahito.com/ とか)';
const PUBLIC_KEY = '公開鍵( https://web-push-codelab.glitch.me/ で取得したやつ )';
const PRIVATE_KEY = '秘密鍵( https://web-push-codelab.glitch.me/ で取得したやつ )';
// push通知認証用のデータ
$subscription = Subscription::create([
'endpoint' => '「必要なトークンを変換して取得(これが重要!!!)」で取得されたendpoint',
'publicKey' => '「必要なトークンを変換して取得(これが重要!!!)」で取得されたuserPublicKey',
'authToken' => '「必要なトークンを変換して取得(これが重要!!!)」で取得されたuserAuthToken',
]);
// ブラウザに認証させる
$auth = [
'VAPID' => [
'subject' => VAPID_SUBJECT,
'publicKey' => PUBLIC_KEY,
'privateKey' => PRIVATE_KEY,
]
];
$webPush = new WebPush($auth);
$report = $webPush->sendOneNotification(
$subscription,
'push通知の本文だよ!'
);
$endpoint = $report->getRequest()->getUri()->__toString();
if ($report->isSuccess()) {
echo '送信成功!';
} else {
echo '送信失敗やで';
}
ここまで用意できたらPHPを実行するだけです。
php SendPush.php
これでブラウザに通知が行くと思います。
以上です。
おわりに
いかがでしたでしょうか?
まーコピペでなんとなく作れるかなーくらいの粒度の記事ですね。
実用的にするのであれば、
「必要なトークンを変換して取得(これが重要!!!)」で取得されたデータは、
DBに保存しておき、SendPush.phpをcronで叩く感じにすればいい感じになるかと。
はい、今回は以上になります。
何かあればコメントいただけると嬉しいです。
それでは、また次回。
Discussion
phpの変数宣言がjavascriptとごっちゃになってます
クラス宣言だったらごめんなさい