mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 10:56:10 +01:00 
			
		
		
		
	Move EventSource to SharedWorker (#12095)
Move EventSource to use a SharedWorker. This prevents issues with HTTP/1.1 open browser connections from preventing gitea from opening multiple tabs. Also allow setting EVENT_SOURCE_UPDATE_TIME to disable EventSource updating Fix #11978 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
		
							
								
								
									
										140
									
								
								web_src/js/features/eventsource.sharedworker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								web_src/js/features/eventsource.sharedworker.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| self.name = 'eventsource.sharedworker.js'; | ||||
|  | ||||
| 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('logout'); | ||||
|     this.listen('notification-count'); | ||||
|     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; | ||||
|     const self = this; | ||||
|     this.eventSource.addEventListener(eventType, (event) => { | ||||
|       self.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.onconnect = (e) => { | ||||
|   for (const port of e.ports) { | ||||
|     port.addEventListener('message', (event) => { | ||||
|       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; | ||||
|         return; | ||||
|       } else if (event.data.type === 'listen') { | ||||
|         const source = sourcesByPort[port]; | ||||
|         source.listen(event.data.eventType); | ||||
|         return; | ||||
|       } 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; | ||||
|         } | ||||
|         return; | ||||
|       } else if (event.data.type === 'status') { | ||||
|         const source = sourcesByPort[port]; | ||||
|         if (!source) { | ||||
|           port.postMessage({ | ||||
|             type: 'status', | ||||
|             message: 'not connected', | ||||
|           }); | ||||
|           return; | ||||
|         } | ||||
|         source.status(port); | ||||
|         return; | ||||
|       } else { | ||||
|         // just send it back | ||||
|         port.postMessage({ | ||||
|           type: 'error', | ||||
|           message: `received but don't know how to handle: ${event.data}`, | ||||
|         }); | ||||
|         return; | ||||
|       } | ||||
|     }); | ||||
|     port.start(); | ||||
|   } | ||||
| }; | ||||
| @@ -18,7 +18,25 @@ export function initNotificationsTable() { | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export function initNotificationCount() { | ||||
| async function receiveUpdateCount(event) { | ||||
|   try { | ||||
|     const data = JSON.parse(event.data); | ||||
|  | ||||
|     const notificationCount = document.querySelector('.notification_count'); | ||||
|     if (data.Count > 0) { | ||||
|       notificationCount.classList.remove('hidden'); | ||||
|     } else { | ||||
|       notificationCount.classList.add('hidden'); | ||||
|     } | ||||
|  | ||||
|     notificationCount.text(`${data.Count}`); | ||||
|     await updateNotificationTable(); | ||||
|   } catch (error) { | ||||
|     console.error(error, event); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export async function initNotificationCount() { | ||||
|   const notificationCount = $('.notification_count'); | ||||
|  | ||||
|   if (!notificationCount.length) { | ||||
| @@ -26,36 +44,57 @@ export function initNotificationCount() { | ||||
|   } | ||||
|  | ||||
|   if (NotificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource) { | ||||
|     // Try to connect to the event source first | ||||
|     const source = new EventSource(`${AppSubUrl}/user/events`); | ||||
|     source.addEventListener('notification-count', async (e) => { | ||||
|       try { | ||||
|         const data = JSON.parse(e.data); | ||||
|  | ||||
|         const notificationCount = $('.notification_count'); | ||||
|         if (data.Count === 0) { | ||||
|           notificationCount.addClass('hidden'); | ||||
|         } else { | ||||
|           notificationCount.removeClass('hidden'); | ||||
|     // Try to connect to the event source via the shared worker first | ||||
|     if (window.SharedWorker) { | ||||
|       const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker'); | ||||
|       worker.addEventListener('error', (event) => { | ||||
|         console.error(event); | ||||
|       }); | ||||
|       worker.port.onmessageerror = () => { | ||||
|         console.error('Unable to deserialize message'); | ||||
|       }; | ||||
|       worker.port.postMessage({ | ||||
|         type: 'start', | ||||
|         url: `${window.location.origin}${AppSubUrl}/user/events`, | ||||
|       }); | ||||
|       worker.port.addEventListener('message', (e) => { | ||||
|         if (!e.data || !e.data.type) { | ||||
|           console.error(e); | ||||
|           return; | ||||
|         } | ||||
|         if (event.data.type === 'notification-count') { | ||||
|           receiveUpdateCount(e.data); | ||||
|           return; | ||||
|         } else if (event.data.type === 'error') { | ||||
|           console.error(e.data); | ||||
|           return; | ||||
|         } else if (event.data.type === 'logout') { | ||||
|           if (e.data !== 'here') { | ||||
|             return; | ||||
|           } | ||||
|           worker.port.postMessage({ | ||||
|             type: 'close', | ||||
|           }); | ||||
|           worker.port.close(); | ||||
|           window.location.href = AppSubUrl; | ||||
|           return; | ||||
|         } else { | ||||
|           return; | ||||
|         } | ||||
|       }); | ||||
|       worker.port.addEventListener('error', (e) => { | ||||
|         console.error(e); | ||||
|       }); | ||||
|       worker.port.start(); | ||||
|       window.addEventListener('beforeunload', () => { | ||||
|         worker.port.postMessage({ | ||||
|           type: 'close', | ||||
|         }); | ||||
|         worker.port.close(); | ||||
|       }); | ||||
|  | ||||
|         notificationCount.text(`${data.Count}`); | ||||
|         await updateNotificationTable(); | ||||
|       } catch (error) { | ||||
|         console.error(error); | ||||
|       } | ||||
|     }); | ||||
|     source.addEventListener('logout', async (e) => { | ||||
|       if (e.data !== 'here') { | ||||
|         return; | ||||
|       } | ||||
|       source.close(); | ||||
|       window.location.href = AppSubUrl; | ||||
|     }); | ||||
|     window.addEventListener('beforeunload', () => { | ||||
|       source.close(); | ||||
|     }); | ||||
|     return; | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (NotificationSettings.MinTimeout <= 0) { | ||||
|   | ||||
| @@ -2432,7 +2432,6 @@ $(document).ready(async () => { | ||||
|   initContextPopups(); | ||||
|   initTableSort(); | ||||
|   initNotificationsTable(); | ||||
|   initNotificationCount(); | ||||
|  | ||||
|   // Repo clone url. | ||||
|   if ($('#repo-clone-url').length > 0) { | ||||
| @@ -2477,6 +2476,7 @@ $(document).ready(async () => { | ||||
|     initClipboard(), | ||||
|     initUserHeatmap(), | ||||
|     initServiceWorker(), | ||||
|     initNotificationCount(), | ||||
|   ]); | ||||
| }); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user