Implement browser push notifications

This commit is contained in:
2025-09-20 20:13:35 +03:00
Unverified
parent 2f30399607
commit 9a25bdcf62
13 changed files with 590 additions and 6 deletions
+1
View File
@@ -19,6 +19,7 @@ import 'mdui/components/text-field';
import 'mdui/components/button-icon';
import 'mdui/components/top-app-bar';
import 'mdui/components/top-app-bar-title';
import 'mdui/components/switch';
import { setColorScheme } from 'mdui/functions/setColorScheme.js';
+148
View File
@@ -0,0 +1,148 @@
import { API_BASE_URL } from "../core/config";
export interface PushSubscriptionData {
endpoint: string;
keys: {
p256dh: string;
auth: string;
};
}
export interface NotificationPayload {
title: string;
body: string;
icon?: string;
image?: string;
tag?: string;
data?: any;
}
class PushNotificationManager {
private registration: ServiceWorkerRegistration | null = null;
private subscription: PushSubscription | null = null;
async initialize(): Promise<boolean> {
if (!("serviceWorker" in navigator) || !("PushManager" in window)) {
console.log("Push messaging is not supported");
return false;
}
try {
this.registration = await navigator.serviceWorker.register("/sw.js");
console.log("Service Worker registered successfully");
return true;
} catch (error) {
console.error("Service Worker registration failed:", error);
return false;
}
}
async requestPermission(): Promise<NotificationPermission> {
if (!this.registration) {
throw new Error("Service Worker not initialized");
}
const permission = await Notification.requestPermission();
return permission;
}
async subscribe(): Promise<PushSubscription | null> {
if (!this.registration) {
throw new Error("Service Worker not initialized");
}
try {
this.subscription = await this.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: this.urlBase64ToUint8Array(
"BPFs0EYyE2XqAuY8vQ8B_ZggkJVhf9NmtKqSPtIKqy7lU0yGcM5qfpBz2ESRxNmC_CPbzoLbhKfF8fkKCFUwIjo"
).slice().buffer
});
console.log("Push subscription successful");
return this.subscription;
} catch (error) {
console.error("Push subscription failed:", error);
return null;
}
}
async sendSubscriptionToServer(token: string): Promise<boolean> {
if (!this.subscription) {
throw new Error("No push subscription available");
}
const subscriptionData: PushSubscriptionData = {
endpoint: this.subscription.endpoint,
keys: {
p256dh: this.arrayBufferToBase64(this.subscription.getKey("p256dh")!),
auth: this.arrayBufferToBase64(this.subscription.getKey("auth")!)
}
};
try {
const response = await fetch(`${API_BASE_URL}/push/subscribe`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify(subscriptionData)
});
return response.ok;
} catch (error) {
console.error("Failed to send subscription to server:", error);
return false;
}
}
async unsubscribe(): Promise<boolean> {
if (!this.subscription) {
return true;
}
try {
const result = await this.subscription.unsubscribe();
this.subscription = null;
return result;
} catch (error) {
console.error("Failed to unsubscribe:", error);
return false;
}
}
private 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;
}
private arrayBufferToBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
let binary = "";
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
getSubscription(): PushSubscription | null {
return this.subscription;
}
isSupported(): boolean {
return "serviceWorker" in navigator && "PushManager" in window;
}
}
export const pushNotificationManager = new PushNotificationManager();