123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- 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 < 0) {
- 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();
- }
- });
|