github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/static_source/admin/src/api/stream.ts (about)

     1  import {ConstantBackoff, Websocket, WebsocketBuilder} from 'websocket-ts';
     2  import {EventHTML5Notify} from '@/api/types';
     3  import {useCache} from "@/hooks/web/useCache";
     4  import {UUID} from "uuid-generator-ts";
     5  import {ref} from "vue";
     6  
     7  const {wsCache} = useCache()
     8  
     9  export const streamStatus = ref<'online' | 'offline'>('offline');
    10  
    11  class Stream {
    12    private ws: Websocket | null = null;
    13    protected subscribers: { [key: string]: any };
    14  
    15    constructor() {
    16      this.subscribers = {};
    17    }
    18  
    19    connect(url: string, accessToken: string): void {
    20      if (this.ws) {
    21        return;
    22      }
    23      url = url.replace("https", "wss")
    24      url = url.replace("http", "ws")
    25      url = url + '/v1/ws?access_token=' + accessToken;
    26  
    27      if (wsCache.get('serverId')) {
    28        url = url + '&server_id=' + wsCache.get('serverId');
    29      }
    30  
    31      this.ws = new WebsocketBuilder(url)
    32        .onOpen((ws: Websocket, ev: Event) => this.onOpen(ws, ev, accessToken))
    33        .onClose((ws: Websocket, ev: CloseEvent) => this.onClose(ws, ev))
    34        .onError((ws: Websocket, ev: Event) => this.error(ws, ev))
    35        .onMessage((ws: Websocket, ev: MessageEvent) => this.onMessage(ws, ev))
    36        .withBackoff(new ConstantBackoff(1000)) // 1000ms = 1s
    37        .onRetry((i, ev) => {
    38          console.log('retry');
    39        })
    40        .build();
    41    }
    42  
    43    disconnect(): void {
    44      if (!this.ws) {
    45        return;
    46      }
    47      this.ws.close();
    48      this.ws = null;
    49    }
    50  
    51    private error(ws: Websocket, ev: Event): any {
    52      streamStatus.value = 'offline'
    53      console.log('error');
    54    }
    55  
    56    private onClose(ws: Websocket, ev: CloseEvent): any {
    57      streamStatus.value = 'offline'
    58      console.log('closed');
    59    }
    60  
    61    private onOpen(ws: Websocket, ev: Event, accessToken: string): any {
    62      streamStatus.value = 'online'
    63      console.log('opened');
    64      ws.send(JSON.stringify({body: btoa('init'), access_token: accessToken}));
    65      ws.send(JSON.stringify({
    66        id: UUID.createUUID(),
    67        query: 'event_get_server_version',
    68      }));
    69    }
    70  
    71    private onMessage(ws: Websocket, ev: MessageEvent): any {
    72      let m: StreamResponse;
    73      try {
    74        const result = JSON.parse(ev.data);
    75        m = result;
    76        const body: any = JSON.parse(atob(result.body));
    77        m.body = body;
    78      } catch {
    79        console.debug('from the stream came a string value');
    80        return;
    81      }
    82  
    83      switch (m.query) {
    84        case 'html5_notify':
    85          return this.html5Notify(ev.data)
    86      }
    87  
    88      if (!this.subscribers) {
    89        return;
    90      }
    91  
    92      if (m.query in this.subscribers) {
    93        for (const id in this.subscribers[m.query]) {
    94          this.subscribers[m.query][id](m.body);
    95        }
    96      }
    97    }
    98  
    99    public send(data: any) {
   100      if (this.ws) {
   101        this.ws.send(JSON.stringify(data));
   102      }
   103    }
   104  
   105    public subscribe(name: string, id: string, f: any) {
   106      console.debug('subscribe', name, id);
   107      if (this.subscribers[name] === undefined) {
   108        this.subscribers[name] = {};
   109      }
   110      this.subscribers[name][id] = f;
   111    }
   112  
   113    public unsubscribe(name: string, id: string) {
   114      console.debug('unsubscribe', name, id);
   115      if (this.subscribers[name] !== undefined) {
   116        if (this.subscribers[name][id] !== undefined) {
   117          delete this.subscribers[name][id];
   118        }
   119      }
   120    }
   121  
   122    public notify(event: EventHTML5Notify) {
   123      if (!('Notification' in window)) {
   124        console.warn('the browser does not support HTML notifications, it needs to be updated');
   125        return;
   126      }
   127      if (Notification.permission === 'granted') {
   128        new Notification(event.title, event.options);
   129        return;
   130      }
   131      if (Notification.permission !== 'denied') {
   132        Notification.requestPermission(function(permission) {
   133          if (permission === 'granted') {
   134            new Notification(event.title, event.options);
   135          }
   136        });
   137      }
   138    }
   139  
   140    private html5Notify(data: string) {
   141      const {body} = JSON.parse(data);
   142      const msg: EventHTML5Notify = JSON.parse(atob(body));
   143      // console.log(msg)
   144      this.notify(msg);
   145    }
   146  }
   147  
   148  const stream = new Stream();
   149  
   150  export default stream;
   151  
   152  export interface StreamResponse {
   153    id: string;
   154    query: string;
   155    body: string;
   156  }
   157  
   158  export interface StreamRequest {
   159    id: string;
   160    query: string;
   161    body: string;
   162  }