141 lines · 3.7 KB · JavaScript
RawDownload
const sourcesByUrl = {};const sourcesByPort = {}; class Source {  constructor(url) {    this.url = url;    this.eventSource = new EventSource(url);    this.listening = {};    this.clients = [];    this.listen('open');    this.listen('close');    this.listen('logout');    this.listen('notification-count');    this.listen('stopwatches');    this.listen('error');  }   register(port) {    if (this.clients.includes(port)) return;     this.clients.push(port);     port.postMessage({      type: 'status',      message: `registered to ${this.url}`,    });  }   deregister(port) {    const portIdx = this.clients.indexOf(port);    if (portIdx === -1) {      return this.clients.length;    }    this.clients.splice(portIdx, 1);    return this.clients.length;  }   close() {    if (!this.eventSource) return;     this.eventSource.close();    this.eventSource = null;  }   listen(eventType) {    if (this.listening[eventType]) return;    this.listening[eventType] = true;    this.eventSource.addEventListener(eventType, (event) => {      this.notifyClients({        type: eventType,        data: event.data,      });    });  }   notifyClients(event) {    for (const client of this.clients) {      client.postMessage(event);    }  }   status(port) {    port.postMessage({      type: 'status',      message: `url: ${this.url} readyState: ${this.eventSource.readyState}`,    });  }} self.addEventListener('connect', (e) => {  for (const port of e.ports) {    port.addEventListener('message', (event) => {      if (!self.EventSource) {        // some browsers (like PaleMoon, Firefox<53) don't support EventSource in SharedWorkerGlobalScope.        // this event handler needs EventSource when doing "new Source(url)", so just post a message back to the caller,        // in case the caller would like to use a fallback method to do its work.        port.postMessage({type: 'no-event-source'});        return;      }      if (event.data.type === 'start') {        const url = event.data.url;        if (sourcesByUrl[url]) {          // we have a Source registered to this url          const source = sourcesByUrl[url];          source.register(port);          sourcesByPort[port] = source;          return;        }        let source = sourcesByPort[port];        if (source) {          if (source.eventSource && source.url === url) return;           // How this has happened I don't understand...          // deregister from that source          const count = source.deregister(port);          // Clean-up          if (count === 0) {            source.close();            sourcesByUrl[source.url] = null;          }        }        // Create a new Source        source = new Source(url);        source.register(port);        sourcesByUrl[url] = source;        sourcesByPort[port] = source;      } else if (event.data.type === 'listen') {        const source = sourcesByPort[port];        source.listen(event.data.eventType);      } else if (event.data.type === 'close') {        const source = sourcesByPort[port];         if (!source) return;         const count = source.deregister(port);        if (count === 0) {          source.close();          sourcesByUrl[source.url] = null;          sourcesByPort[port] = null;        }      } else if (event.data.type === 'status') {        const source = sourcesByPort[port];        if (!source) {          port.postMessage({            type: 'status',            message: 'not connected',          });          return;        }        source.status(port);      } else {        // just send it back        port.postMessage({          type: 'error',          message: `received but don't know how to handle: ${event.data}`,        });      }    });    port.start();  }});