
これまで、スマホアプリにできて、SPA(Single Page Application)や、WPA(Progressive Web Apps)にはできないと思っていたPush通知が最近できてしまうという話を聞きました。
調べてみると、「Web push」という技術を使って実現可能だそうです。
知らないことは、チャレンジする精神の自分としては、この技術を知らないわけにはいきません。
ということで、今回は「Web push」にトライしてみたいと思います。
興味のある人は、是非ご自身でもコーディングして技術習得することをオススメします。
ちなみに、今回は、Web pushの調査と構築手順までの内容になっていて、そこからの展開などについては後日のブログに掲載したいと思います。
前提知識と準備
Web pushは、Node.jsの「web-push」ライブラリを使う事実現できます。
web-pushには、次の3つの環境が必要になります。
1. ブラウザ(受信)
2. 自前サーバー(送信指示)
3. プッシュサービス(中継:Google/Mozilla等)
今回はlocalhostで、自分端末内に構築しますが、公開する場合は、SSLが必須になります。
web-pushの仕組み(構築・動作フロー)
1. 鍵の生成
送信者を証明するVAPIDキー(公開鍵・秘密鍵)を用意する。
2. 購読(Subscribe)
ブラウザがユーザーの許可を得て、プッシュサービスから「宛先(Endpoint)」を取得し、サーバーに保存する。
3. 送信(Push)
サーバーが秘密鍵を使ってメッセージを作成し、プッシュサービスへ送る。
Dockerで環境構築
package.json
{
"name": "webpush-local",
"version": "1.0.0",
"main": "server.js",
"dependencies": {
"express": "^4.18.2",
"web-push": "^3.6.6",
"body-parser": "^1.20.2"
}
}
server.js (サーバー側)
VAPIDキーの生成、購読情報の保存、プッシュ送信を行うメインスクリプトです。
const express = require('express');
const webpush = require('web-push');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'public')));
// 1. VAPIDキーの生成(本来は一度生成したら固定する)
const vapidKeys = webpush.generateVAPIDKeys();
webpush.setVapidDetails('mailto:example@yourdomain.com', vapidKeys.publicKey, vapidKeys.privateKey);
let subscription = {}; // 本来はDBに保存
// 公開鍵をフロントに渡す
app.get('/vapidPublicKey', (req, res) => res.send(vapidKeys.publicKey));
// 購読情報の保存
app.post('/subscribe', (req, res) => {
subscription = req.body;
res.status(201).json({});
});
// プッシュ通知のトリガー
app.post('/sendNotification', (req, res) => {
const payload = JSON.stringify({ title: 'Hello from Local!', body: 'WebPush成功です!' });
webpush.sendNotification(subscription, payload).catch(err => console.error(err));
res.status(200).json({});
});
app.listen(3000, () => console.log('Server started on port 3000'));
Dockerfile
FROM node:18-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
compose.yaml
services:
webpush-app:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
フロントエンドの準備
public フォルダを作成し、その中に index.html と sw.js(Service Worker)を置きます。
index.html: 「通知を許可」ボタンを配置。
sw.js: プッシュイベントを受け取って表示する処理を記述。
ポイント: WebPushはService Workerが必須です。localhostならHTTPS化せずに動作します。
実行方法
1. Dockerを実行
コマンドラインで docker-compose up を実行します。
2. ブラウザアクセス
ブラウザで http://localhost:3000 を開きます。
3. 通知受信
開発者ツールのコンソール等で購読処理を走らせ(sw.jsを登録)、/sendNotification を叩けば通知が届きます。
もっと手軽に試したい場合
「コードを書くのが面倒」という場合は、"
ntfy" というオープンソースの通知サーバーを使うのが最も簡単です。
Docker一行で起動し、ブラウザ通知もサポートしています。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ntfy WebPush Test</title>
<style>
body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; background: #f0f2f5; }
.card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); text-align: center; }
button { padding: 10px 20px; font-size: 1rem; cursor: pointer; background: #007bff; color: white; border: none; border-radius: 4px; }
#status { margin-top: 1rem; color: #666; }
</style>
</head>
<body>
<div class="card">
<h2>ntfy 通知テスト</h2>
<p>トピック名: <strong>mytopic</strong></p>
<button id="enableBtn">通知を許可して待機</button>
<div id="status">ボタンを押してください</div>
</div>
<script>
const topic = 'mytopic'; // 購読するトピック名
const ntfyUrl = `http://localhost:8080/${topic}/sse`; // ローカルサーバーのURL
const enableBtn = document.getElementById('enableBtn');
const statusDiv = document.getElementById('status');
enableBtn.addEventListener('click', async () => {
// 1. 通知の許可を求める
const permission = await Notification.requestPermission();
if (permission === 'granted') {
statusDiv.innerText = '通知待機中... (mytopic)';
enableBtn.disabled = true;
// 2. ntfyサーバーに接続 (SSE)
const eventSource = new EventSource(ntfyUrl);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('受信データ:', data);
// 3. ブラウザ通知を表示
new Notification("ntfyからの通知", {
body: data.message || "メッセージが届きました",
icon: "https://ntfy.sh/static/img/ntfy.png"
});
};
eventSource.onerror = (err) => {
console.error("接続エラー:", err);
statusDiv.innerText = "接続に失敗しました。サーバーが起動しているか確認してください。";
};
} else {
statusDiv.innerText = "通知が拒否されました。";
}
});
</script>
</body>
</html>
[bash]
docker run -p 80:80 binwiederhier/ntfy serve
これを起動して http://localhost にアクセスするだけで、WebPushのテスト環境が即座に手に入ります。
以下の動画(英語)で解説されています。
ブラウザの挙動について
PCブラウザとスマホブラウザで、それぞれ挙動が変わるため、以下にまとめておきます。
PCブラウザの挙動
・ネイティブにWeb Push通知が可能
・ブラウザを閉じていても通知が届く
・Service Workerが常駐的に機能する
・比較的実装が安定している
特徴
・Chrome / Edge / Firefox:問題なく対応
・Safari(macOS):対応しているが独自仕様あり
・インストール不要で通知可能
スマホブラウザの挙動(android)
・Android Chrome は PCとほぼ同等に対応
・ブラウザを閉じていても通知が届く
・PWA化するとアプリ並みの挙動が可能
特徴
・Android + Chrome:実用レベル
・ホーム画面追加(PWA)でUX向上
・実装はPCとほぼ共通
スマホブラウザの挙動(iOS)
・iOS 16.4以降 + PWA限定で対応
・Safari単体では不可
特徴
・Webサイトを「ホーム画面に追加」する必要あり
・PWAとして起動した場合のみ通知可能
・通知許可フローがやや分かりにくい
技術選定の目安
・PC+Android中心 → Web Pushで十分
・iPhoneにも確実に届けたい → ネイティブアプリ or PWA前提
・実装コストを下げたい → 外部Pushサービス
注意点
・通知許可の出し方次第で拒否率が大きく変わる
・通知が多すぎるとブロックされやすい
・iOSは特に制約が多い
あとがき
iPhoneはやはりネイティブアプリじゃないとPush通知できないという事がわかりました。
ただ、iOS16.4以降でPWAに限っては、可能なようです。(あまり便利には思えないんですが・・・)
今回調べてわかったのは、PCブラウザは比較的手軽にPUSH通知可能なんですね。
スマホとPCは、近いようでまだまだ距離のある存在なのかもしれませんね。
そして、AndroidとiPhoneも同じスマホといえども、ここまで挙動が違うというのもなんともエンジニア泣かせの存在です。
スマホの挙動を統一することで、世の中の情報伝達がスムーズに進むことになるので、世界的標準ルールなどが今後できると良いのにな〜と考えてしまいました。
0 件のコメント:
コメントを投稿