github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/static_source/admin/src/api/pushService.ts (about) 1 import stream from "@/api/stream"; 2 import {UUID} from "uuid-generator-ts"; 3 import {EventNewWebPushPublicKey, EventUserDevices, ServerSubscription,} from "@/api/types"; 4 import {GetFullUrl} from "@/utils/serverId"; 5 6 const uuid = new UUID() 7 8 class PushService { 9 private worker: IpostMessage | null = null; 10 private pushManager: PushManager | null = null; 11 private currentID: string = uuid.getDashFreeUUID(); 12 private serverSubscriptions: ServerSubscription[] | null = null; 13 private publicKey: string | null = null; 14 private isStarted = false; 15 16 constructor() { 17 } 18 19 start() { 20 if (this.isStarted) return; 21 this.isStarted = true 22 23 setTimeout(() => { 24 stream.subscribe('event_user_devices', this.currentID, this.eventUserDevices()) 25 stream.subscribe('event_new_webpush_public_key', this.currentID, this.eventNewWebpushPublicKey()) 26 this.registerWorker() 27 }, 1000) 28 } 29 30 shutdown() { 31 if (!this.isStarted) return; 32 this.isStarted = false 33 34 stream.unsubscribe('event_user_device', this.currentID) 35 stream.unsubscribe('event_new_webpush_public_key', this.currentID) 36 } 37 38 public async checkSubscription() { 39 console.debug('Проверка подписок ...') 40 if (!this.pushManager) { 41 console.debug('pushManager не определен') 42 return 43 } 44 try { 45 const currentSubscription = await this.pushManager.getSubscription(); 46 if (currentSubscription) { 47 // Проверяем текущую подписку и принимаем решение обновить или использовать её 48 const shouldRenewSubscription = this.checkIfSubscriptionNeedsRenewal(currentSubscription); 49 if (shouldRenewSubscription) { 50 await this.renewSubscription(currentSubscription); 51 } else { 52 // Используем текущую подписку 53 // console.debug(JSON.stringify(currentSubscription)) 54 console.debug('Используем текущую подписку на уведомления'); 55 } 56 } else { 57 // Создаем новую подписку 58 await this.createNewSubscription(); 59 } 60 } catch (error) { 61 console.error('Ошибка при обработке подписки на уведомления:', error); 62 } 63 64 } 65 66 private checkIfSubscriptionNeedsRenewal(clientSubscription: PushSubscription): boolean { 67 68 if (this.serverSubscriptions) { 69 console.debug('Проверяем подписку на сервере') 70 const matchingServerSubscription = this.serverSubscriptions.find(serverSubscription => serverSubscription.endpoint === clientSubscription.endpoint); 71 if (!matchingServerSubscription) { 72 console.debug('Подписка не найдена на сервере, нужно обновить подписку') 73 return true; 74 } 75 } 76 77 // Если срок действия подписки указан и он истек, то нужно обновить подписку 78 if (clientSubscription.expirationTime && clientSubscription.expirationTime < Date.now()) { 79 return true; 80 } 81 82 // Возвращаем false, если подписка не требует обновления 83 return false; 84 } 85 86 // ------------------------------------------ 87 // push manager 88 // ------------------------------------------ 89 private async renewSubscription(clientSubscription: PushSubscription) { 90 console.debug('Обновление подписки') 91 if (!this.publicKey || !this.pushManager) { 92 console.debug('pushManager или publicKey не определен', this.publicKey, this.pushManager) 93 return 94 } 95 try { 96 // Получаем новую подписку от сервиса уведомлений 97 await this.createNewSubscription() 98 99 // Отменяем текущую подписку, чтобы избежать дублирования уведомлений 100 await clientSubscription.unsubscribe(); 101 102 console.debug('Подписка успешно обновлена'); 103 } catch (error) { 104 console.error('Ошибка при обновлении подписки на уведомления:', error); 105 } 106 } 107 108 private async createNewSubscription() { 109 console.debug('Создаем новую подписку') 110 if (!this.publicKey || !this.pushManager) { 111 // console.debug('pushManager или publicKey не определен', this.publicKey, this.pushManager) 112 return 113 } 114 // Создаем новую подписку 115 const options = { 116 applicationServerKey: this.publicKey, 117 userVisibleOnly: true, 118 } 119 const subscription = await this.pushManager.subscribe(options) 120 // console.debug(JSON.stringify(subscription)) 121 stream.send({ 122 id: UUID.createUUID(), 123 query: 'event_add_webpush_subscription', 124 body: btoa(JSON.stringify(subscription)) 125 }); 126 } 127 128 // ------------------------------------------ 129 // service worker 130 // ------------------------------------------ 131 132 // private postMessage(msg: IMessageType) { 133 // if (!this.worker) return; 134 // this.worker.postMessage(msg) 135 // } 136 // 137 // private onmessage(ev: MessageEvent) { 138 // //receive user registration data 139 // } 140 141 private get getUrl(): string { 142 if (window?.app_settings?.server_version) { 143 return GetFullUrl('/sw.js') 144 } else { 145 return '/sw-dev.js' 146 } 147 } 148 149 private async registerWorker() { 150 if (!('serviceWorker' in navigator)) { 151 throw new Error('No Service Worker support!') 152 } 153 if (!('PushManager' in window)) { 154 throw new Error('No Push API Support!') 155 } 156 157 await this.requestNotificationPermission(); 158 159 // navigator.serviceWorker.onmessage = this.onmessage 160 161 const mode = window?.app_settings?.server_version?'classic':'module' 162 // const mode = 'classic' 163 navigator.serviceWorker.getRegistration(this.getUrl, {type: mode}).then((reg: ServiceWorkerRegistration) => { 164 if (reg && reg.active) { 165 // reg.update() 166 this.worker = reg.active 167 this.pushManager = reg.pushManager; 168 this.fetchUserDevices() 169 this.fetchPublicKey() 170 return 171 } 172 // if (!this.worker) { 173 // navigator.serviceWorker.register(this.getUrl, {type: mode}).then((reg: ServiceWorkerRegistration) => { 174 // if (reg && reg.active) { 175 // this.worker = reg.active 176 // this.pushManager = reg.pushManager; 177 // this.fetchUserDevices() 178 // this.fetchPublicKey() 179 // } 180 // }) 181 // } 182 }) 183 } 184 185 private requestNotificationPermission = async () => { 186 const permission = await window.Notification.requestPermission(); 187 // value of permission can be 'granted', 'default', 'denied' 188 // granted: user has accepted the request 189 // default: user has dismissed the notification permission popup by clicking on x 190 // denied: user has denied the request. 191 if (permission !== 'granted') { 192 throw new Error('Permission not granted for Notification'); 193 } 194 } 195 196 // ------------------------------------------ 197 // events 198 // ------------------------------------------ 199 private fetchPublicKey() { 200 stream.send({ 201 id: UUID.createUUID(), 202 query: 'event_get_webpush_public_key', 203 }); 204 } 205 206 private fetchUserDevices() { 207 stream.send({ 208 id: UUID.createUUID(), 209 query: 'event_get_user_devices', 210 }); 211 } 212 213 private eventNewWebpushPublicKey() { 214 return (data: EventNewWebPushPublicKey) => { 215 // console.debug(data.public_key) 216 if (data.public_key) { 217 this.publicKey = data.public_key; 218 } 219 this.checkSubscription() 220 } 221 } 222 223 private eventUserDevices() { 224 return (data: EventUserDevices) => { 225 // console.debug(data) 226 if (data) { 227 this.serverSubscriptions = data.subscription; 228 } 229 this.checkSubscription() 230 } 231 } 232 } 233 234 const pushService: PushService = new PushService() 235 236 export default pushService; 237 238 export interface IpostMessage { 239 postMessage(message: any, transfer: Transferable[]): void; 240 } 241 242 export interface IMessageType { 243 t: string 244 data: any 245 } 246