goldenChat base source add
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* Web Push 알림 유틸리티
|
||||
* Service Worker 등록 및 Push 구독 관리
|
||||
*
|
||||
* ⚠️ 주의: Service Worker와 Web Push는 HTTPS 또는 localhost에서만 작동합니다!
|
||||
* HTTP 환경에서는 브라우저 알림만 사용하세요.
|
||||
*/
|
||||
|
||||
// VAPID 공개 키 (백엔드에서 생성)
|
||||
// 주의: 이 키는 백엔드 application.yml의 push.vapid.public-key와 동일해야 합니다!
|
||||
const VAPID_PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4xZ0NOe_lJmuwRmNvEy8sokT3IqDxQiKBOx8YRJfw3fbURO36jPl0WC-SD9ZKbgldcnfUIYJHHOdCmanShM90g';
|
||||
|
||||
/**
|
||||
* HTTPS 환경 확인
|
||||
* Service Worker는 HTTPS 또는 localhost에서만 작동
|
||||
*/
|
||||
export const isSecureContext = (): boolean => {
|
||||
// HTTPS 또는 localhost 확인
|
||||
return window.isSecureContext ||
|
||||
window.location.protocol === 'https:' ||
|
||||
window.location.hostname === 'localhost' ||
|
||||
window.location.hostname === '127.0.0.1';
|
||||
};
|
||||
|
||||
/**
|
||||
* PWA Web Push 지원 확인
|
||||
*/
|
||||
export const isPWAPushSupported = (): boolean => {
|
||||
return isSecureContext() && 'serviceWorker' in navigator && 'PushManager' in window;
|
||||
};
|
||||
|
||||
/**
|
||||
* Service Worker 등록
|
||||
* ⚠️ HTTPS 또는 localhost에서만 작동
|
||||
*/
|
||||
export const registerServiceWorker = async (): Promise<ServiceWorkerRegistration | null> => {
|
||||
if (!isSecureContext()) {
|
||||
console.warn('[Push] ⚠️ HTTP 환경에서는 Service Worker가 작동하지 않습니다.');
|
||||
console.warn('[Push] HTTPS를 사용하거나 localhost에서 실행하세요.');
|
||||
console.warn('[Push] 현재는 브라우저 알림만 사용됩니다.');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
console.warn('[Push] Service Worker not supported in this browser');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register('/service-worker.js', {
|
||||
scope: '/',
|
||||
});
|
||||
|
||||
console.log('[Push] ✅ Service Worker registered:', registration);
|
||||
|
||||
// Wait for the service worker to be ready
|
||||
await navigator.serviceWorker.ready;
|
||||
console.log('[Push] ✅ Service Worker ready');
|
||||
|
||||
return registration;
|
||||
} catch (error) {
|
||||
console.error('[Push] ❌ Service Worker registration failed:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 알림 권한 요청
|
||||
*/
|
||||
export const requestNotificationPermission = async (): Promise<NotificationPermission> => {
|
||||
if (!('Notification' in window)) {
|
||||
console.warn('Notifications not supported in this browser');
|
||||
return 'denied';
|
||||
}
|
||||
|
||||
if (Notification.permission === 'granted') {
|
||||
return 'granted';
|
||||
}
|
||||
|
||||
if (Notification.permission === 'denied') {
|
||||
return 'denied';
|
||||
}
|
||||
|
||||
const permission = await Notification.requestPermission();
|
||||
console.log('[Push] Notification permission:', permission);
|
||||
return permission;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base64를 Uint8Array로 변환 (VAPID 키 변환용)
|
||||
*/
|
||||
const urlBase64ToUint8Array = (base64String: string): Uint8Array => {
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Push 구독 생성
|
||||
*/
|
||||
export const subscribeToPush = async (): Promise<PushSubscription | null> => {
|
||||
try {
|
||||
// Service Worker 등록 확인
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
|
||||
// 기존 구독 확인
|
||||
let subscription = await registration.pushManager.getSubscription();
|
||||
|
||||
if (subscription) {
|
||||
console.log('[Push] Already subscribed:', subscription);
|
||||
return subscription;
|
||||
}
|
||||
|
||||
// 알림 권한 요청
|
||||
const permission = await requestNotificationPermission();
|
||||
if (permission !== 'granted') {
|
||||
console.warn('[Push] Notification permission not granted');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 새 구독 생성
|
||||
subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
|
||||
});
|
||||
|
||||
console.log('[Push] New subscription created:', subscription);
|
||||
return subscription;
|
||||
} catch (error) {
|
||||
console.error('[Push] Failed to subscribe:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Push 구독 해제
|
||||
*/
|
||||
export const unsubscribeFromPush = async (): Promise<boolean> => {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
|
||||
if (!subscription) {
|
||||
console.log('[Push] No subscription to unsubscribe');
|
||||
return true;
|
||||
}
|
||||
|
||||
const result = await subscription.unsubscribe();
|
||||
console.log('[Push] Unsubscribed:', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('[Push] Failed to unsubscribe:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 현재 구독 상태 확인
|
||||
*/
|
||||
export const getSubscription = async (): Promise<PushSubscription | null> => {
|
||||
try {
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
return subscription;
|
||||
} catch (error) {
|
||||
console.error('[Push] Failed to get subscription:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 구독 정보를 서버로 전송
|
||||
*/
|
||||
export const sendSubscriptionToServer = async (
|
||||
subscription: PushSubscription,
|
||||
endpoint: string = '/api/user/settings/push-subscription'
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
if (!token) {
|
||||
console.warn('[Push] No access token found');
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
subscription: subscription.toJSON(),
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to send subscription: ${response.statusText}`);
|
||||
}
|
||||
|
||||
console.log('[Push] Subscription sent to server');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Push] Failed to send subscription to server:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 구독 삭제를 서버에 알림
|
||||
*/
|
||||
export const deleteSubscriptionFromServer = async (
|
||||
endpoint: string = '/api/user/settings/push-subscription'
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
if (!token) {
|
||||
console.warn('[Push] No access token found');
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to delete subscription: ${response.statusText}`);
|
||||
}
|
||||
|
||||
console.log('[Push] Subscription deleted from server');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Push] Failed to delete subscription from server:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PWA 초기화 (Service Worker 등록 + Push 구독)
|
||||
*/
|
||||
export const initializePWA = async (): Promise<void> => {
|
||||
console.log('[PWA] Initializing...');
|
||||
|
||||
// Service Worker 등록
|
||||
const registration = await registerServiceWorker();
|
||||
if (!registration) {
|
||||
console.warn('[PWA] Service Worker registration failed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Push 구독은 사용자가 설정에서 수동으로 활성화
|
||||
console.log('[PWA] Initialized. Push subscription available in settings.');
|
||||
};
|
||||
|
||||
/**
|
||||
* 테스트 알림 표시
|
||||
*/
|
||||
export const showTestNotification = async (): Promise<void> => {
|
||||
const permission = await requestNotificationPermission();
|
||||
|
||||
if (permission !== 'granted') {
|
||||
alert('알림 권한이 거부되었습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
|
||||
await registration.showNotification('테스트 알림', {
|
||||
body: '푸시 알림이 정상적으로 작동합니다! 🎉',
|
||||
icon: '/favicon.ico',
|
||||
badge: '/favicon.ico',
|
||||
vibrate: [200, 100, 200],
|
||||
tag: 'test-notification',
|
||||
requireInteraction: false,
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user