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