・プッシュ通知の修正
・メニューをスマホに最適化 ・アラート送信済みの条件が再度発動しないように修正
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ArrowLeft, BellRing, Link, Pencil, Plus, Trash2 } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { ArrowLeft, BellOff, BellRing, Link, Pencil, Plus, Trash2 } from 'lucide-react';
|
||||
import { request } from '../api/client.js';
|
||||
import { ConfirmDialog } from '../components/ConfirmDialog.jsx';
|
||||
import { Field } from '../components/Field.jsx';
|
||||
@@ -29,15 +29,32 @@ function urlBase64ToUint8Array(base64String) {
|
||||
}
|
||||
|
||||
function permissionText(permission) {
|
||||
if (permission === 'unsupported') return '非対応';
|
||||
if (permission === 'granted') return '許可済み';
|
||||
if (permission === 'denied') return '拒否されています';
|
||||
return '未設定';
|
||||
}
|
||||
|
||||
function pushStatusText(status) {
|
||||
if (status === 'checking') return '確認中';
|
||||
if (status === 'registered') return 'このブラウザは登録済みです';
|
||||
if (status === 'unregistered') return 'このブラウザは未登録です';
|
||||
if (status === 'no-subscription') return 'このブラウザは未登録です';
|
||||
if (status === 'permission-denied') return 'ブラウザ通知が拒否されています';
|
||||
if (status === 'unconfigured') return 'VAPID public key が未設定です';
|
||||
if (status === 'unsupported') return 'このブラウザは非対応です';
|
||||
if (status === 'error') return '状態を確認できませんでした';
|
||||
return '未確認';
|
||||
}
|
||||
|
||||
async function getPushRegistration() {
|
||||
return navigator.serviceWorker.register('/push-sw.js');
|
||||
}
|
||||
|
||||
export function NotificationMethodsView({ onBack }) {
|
||||
const [webhooks, setWebhooks] = useState([]);
|
||||
const [pushSubscriptions, setPushSubscriptions] = useState([]);
|
||||
const [vapidPublicKey, setVapidPublicKey] = useState('');
|
||||
const [currentPushStatus, setCurrentPushStatus] = useState('unchecked');
|
||||
const [form, setForm] = useState({ alias: '', url: '' });
|
||||
const [editingId, setEditingId] = useState('');
|
||||
const [permission, setPermission] = useState(
|
||||
@@ -46,16 +63,58 @@ export function NotificationMethodsView({ onBack }) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const { showToast } = useToast();
|
||||
|
||||
async function loadMethods() {
|
||||
const refreshCurrentPushStatus = useCallback(
|
||||
async (publicKey = vapidPublicKey) => {
|
||||
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
|
||||
setCurrentPushStatus('unsupported');
|
||||
return;
|
||||
}
|
||||
if (!publicKey) {
|
||||
setCurrentPushStatus('unconfigured');
|
||||
return;
|
||||
}
|
||||
if (typeof Notification === 'undefined') {
|
||||
setCurrentPushStatus('unsupported');
|
||||
return;
|
||||
}
|
||||
setPermission(Notification.permission);
|
||||
if (Notification.permission === 'denied') {
|
||||
setCurrentPushStatus('permission-denied');
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentPushStatus('checking');
|
||||
try {
|
||||
const registration = await getPushRegistration();
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
if (!subscription) {
|
||||
setCurrentPushStatus('no-subscription');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await request('/api/notification-methods/push-subscription-status', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ endpoint: subscription.endpoint }),
|
||||
});
|
||||
setCurrentPushStatus(data.registered ? 'registered' : 'unregistered');
|
||||
} catch (err) {
|
||||
setCurrentPushStatus('error');
|
||||
showToast({ type: 'error', message: err.message });
|
||||
}
|
||||
},
|
||||
[showToast, vapidPublicKey],
|
||||
);
|
||||
|
||||
const loadMethods = useCallback(async () => {
|
||||
const data = await request('/api/notification-methods');
|
||||
setWebhooks(data.webhooks);
|
||||
setPushSubscriptions(data.pushSubscriptions);
|
||||
setVapidPublicKey(data.vapidPublicKey);
|
||||
}
|
||||
await refreshCurrentPushStatus(data.vapidPublicKey);
|
||||
}, [refreshCurrentPushStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
loadMethods().catch((err) => showToast({ type: 'error', message: err.message }));
|
||||
}, [showToast]);
|
||||
}, [loadMethods, showToast]);
|
||||
|
||||
async function submitWebhook(event) {
|
||||
event.preventDefault();
|
||||
@@ -110,6 +169,9 @@ export function NotificationMethodsView({ onBack }) {
|
||||
if (!vapidPublicKey) {
|
||||
throw new Error('VAPID public key が設定されていません');
|
||||
}
|
||||
if (typeof Notification === 'undefined') {
|
||||
throw new Error('このブラウザはプッシュ通知に対応していません');
|
||||
}
|
||||
|
||||
const nextPermission = await Notification.requestPermission();
|
||||
setPermission(nextPermission);
|
||||
@@ -117,18 +179,49 @@ export function NotificationMethodsView({ onBack }) {
|
||||
throw new Error('ブラウザ通知が許可されませんでした');
|
||||
}
|
||||
|
||||
const registration = await navigator.serviceWorker.register('/push-sw.js');
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
|
||||
});
|
||||
const registration = await getPushRegistration();
|
||||
const existingSubscription = await registration.pushManager.getSubscription();
|
||||
const subscription =
|
||||
existingSubscription ??
|
||||
(await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
|
||||
}));
|
||||
|
||||
await request('/api/notification-methods/push-subscriptions', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(subscription.toJSON()),
|
||||
});
|
||||
showToast({ type: 'success', message: 'プッシュ通知を登録しました' });
|
||||
await loadMethods();
|
||||
await refreshCurrentPushStatus();
|
||||
} catch (err) {
|
||||
showToast({ type: 'error', message: err.message });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function unsubscribePush() {
|
||||
setBusy(true);
|
||||
try {
|
||||
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
|
||||
throw new Error('このブラウザはプッシュ通知に対応していません');
|
||||
}
|
||||
|
||||
const registration = await getPushRegistration();
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
if (!subscription) {
|
||||
await refreshCurrentPushStatus();
|
||||
throw new Error('解除対象のプッシュ通知登録が見つかりません');
|
||||
}
|
||||
|
||||
await request('/api/notification-methods/push-subscriptions', {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({ endpoint: subscription.endpoint }),
|
||||
});
|
||||
await subscription.unsubscribe();
|
||||
showToast({ type: 'success', message: 'プッシュ通知を解除しました' });
|
||||
await refreshCurrentPushStatus();
|
||||
} catch (err) {
|
||||
showToast({ type: 'error', message: err.message });
|
||||
} finally {
|
||||
@@ -239,35 +332,45 @@ export function NotificationMethodsView({ onBack }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="method-list">
|
||||
<article className="method-row">
|
||||
<div>
|
||||
<strong>現在のブラウザ</strong>
|
||||
<span>{pushStatusText(currentPushStatus)}</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div className="push-actions">
|
||||
<button
|
||||
className="secondary"
|
||||
type="button"
|
||||
onClick={subscribePush}
|
||||
disabled={busy || !vapidPublicKey}
|
||||
>
|
||||
<BellRing aria-hidden="true" size={18} />
|
||||
このブラウザを登録
|
||||
</button>
|
||||
{currentPushStatus === 'registered' ? (
|
||||
<ConfirmDialog
|
||||
title="プッシュ通知を解除"
|
||||
description="現在のブラウザをプッシュ通知の登録から解除します。"
|
||||
confirmLabel="解除"
|
||||
onConfirm={unsubscribePush}
|
||||
disabled={busy}
|
||||
trigger={
|
||||
<button className="secondary danger-text" type="button" disabled={busy}>
|
||||
<BellOff aria-hidden="true" size={18} />
|
||||
このブラウザを解除
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
className="secondary"
|
||||
type="button"
|
||||
onClick={subscribePush}
|
||||
disabled={busy || !vapidPublicKey}
|
||||
>
|
||||
<BellRing aria-hidden="true" size={18} />
|
||||
このブラウザを登録
|
||||
</button>
|
||||
)}
|
||||
{!vapidPublicKey ? (
|
||||
<p className="muted">VAPID public key を設定すると登録できます。</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="method-list">
|
||||
{pushSubscriptions.length === 0 ? (
|
||||
<div className="empty">登録済みのブラウザはありません。</div>
|
||||
) : (
|
||||
pushSubscriptions.map((subscription) => (
|
||||
<article className="method-row" key={subscription.notificationMethodId}>
|
||||
<div>
|
||||
<strong>Browser Push</strong>
|
||||
<span>{subscription.endpoint}</span>
|
||||
</div>
|
||||
</article>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user