code.gitea.io/gitea@v1.22.3/web_src/js/features/eventsource.sharedworker.js (about) 1 const sourcesByUrl = {}; 2 const sourcesByPort = {}; 3 4 class Source { 5 constructor(url) { 6 this.url = url; 7 this.eventSource = new EventSource(url); 8 this.listening = {}; 9 this.clients = []; 10 this.listen('open'); 11 this.listen('close'); 12 this.listen('logout'); 13 this.listen('notification-count'); 14 this.listen('stopwatches'); 15 this.listen('error'); 16 } 17 18 register(port) { 19 if (this.clients.includes(port)) return; 20 21 this.clients.push(port); 22 23 port.postMessage({ 24 type: 'status', 25 message: `registered to ${this.url}`, 26 }); 27 } 28 29 deregister(port) { 30 const portIdx = this.clients.indexOf(port); 31 if (portIdx < 0) { 32 return this.clients.length; 33 } 34 this.clients.splice(portIdx, 1); 35 return this.clients.length; 36 } 37 38 close() { 39 if (!this.eventSource) return; 40 41 this.eventSource.close(); 42 this.eventSource = null; 43 } 44 45 listen(eventType) { 46 if (this.listening[eventType]) return; 47 this.listening[eventType] = true; 48 this.eventSource.addEventListener(eventType, (event) => { 49 this.notifyClients({ 50 type: eventType, 51 data: event.data, 52 }); 53 }); 54 } 55 56 notifyClients(event) { 57 for (const client of this.clients) { 58 client.postMessage(event); 59 } 60 } 61 62 status(port) { 63 port.postMessage({ 64 type: 'status', 65 message: `url: ${this.url} readyState: ${this.eventSource.readyState}`, 66 }); 67 } 68 } 69 70 self.addEventListener('connect', (e) => { 71 for (const port of e.ports) { 72 port.addEventListener('message', (event) => { 73 if (!self.EventSource) { 74 // some browsers (like PaleMoon, Firefox<53) don't support EventSource in SharedWorkerGlobalScope. 75 // this event handler needs EventSource when doing "new Source(url)", so just post a message back to the caller, 76 // in case the caller would like to use a fallback method to do its work. 77 port.postMessage({type: 'no-event-source'}); 78 return; 79 } 80 if (event.data.type === 'start') { 81 const url = event.data.url; 82 if (sourcesByUrl[url]) { 83 // we have a Source registered to this url 84 const source = sourcesByUrl[url]; 85 source.register(port); 86 sourcesByPort[port] = source; 87 return; 88 } 89 let source = sourcesByPort[port]; 90 if (source) { 91 if (source.eventSource && source.url === url) return; 92 93 // How this has happened I don't understand... 94 // deregister from that source 95 const count = source.deregister(port); 96 // Clean-up 97 if (count === 0) { 98 source.close(); 99 sourcesByUrl[source.url] = null; 100 } 101 } 102 // Create a new Source 103 source = new Source(url); 104 source.register(port); 105 sourcesByUrl[url] = source; 106 sourcesByPort[port] = source; 107 } else if (event.data.type === 'listen') { 108 const source = sourcesByPort[port]; 109 source.listen(event.data.eventType); 110 } else if (event.data.type === 'close') { 111 const source = sourcesByPort[port]; 112 113 if (!source) return; 114 115 const count = source.deregister(port); 116 if (count === 0) { 117 source.close(); 118 sourcesByUrl[source.url] = null; 119 sourcesByPort[port] = null; 120 } 121 } else if (event.data.type === 'status') { 122 const source = sourcesByPort[port]; 123 if (!source) { 124 port.postMessage({ 125 type: 'status', 126 message: 'not connected', 127 }); 128 return; 129 } 130 source.status(port); 131 } else { 132 // just send it back 133 port.postMessage({ 134 type: 'error', 135 message: `received but don't know how to handle: ${event.data}`, 136 }); 137 } 138 }); 139 port.start(); 140 } 141 });