summaryrefslogtreecommitdiffstats
path: root/web_src/js
diff options
context:
space:
mode:
authorzeripath <art27@cantab.net>2020-07-03 10:55:36 +0100
committerGitHub <noreply@github.com>2020-07-03 10:55:36 +0100
commitae56411e9f78415745c235fb92fbb7abc139b966 (patch)
tree20ce8e22055394dbf80ee9355cb5c1a03cb19479 /web_src/js
parentcd994f635427388d4e2abb6612e0b830a8f485b9 (diff)
downloadgitea-ae56411e9f78415745c235fb92fbb7abc139b966.tar.gz
gitea-ae56411e9f78415745c235fb92fbb7abc139b966.zip
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>
Diffstat (limited to 'web_src/js')
-rw-r--r--web_src/js/features/eventsource.sharedworker.js140
-rw-r--r--web_src/js/features/notification.js97
-rw-r--r--web_src/js/index.js2
3 files changed, 209 insertions, 30 deletions
diff --git a/web_src/js/features/eventsource.sharedworker.js b/web_src/js/features/eventsource.sharedworker.js
new file mode 100644
index 0000000000..7dccd8994e
--- /dev/null
+++ b/web_src/js/features/eventsource.sharedworker.js
@@ -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();
+ }
+};
diff --git a/web_src/js/features/notification.js b/web_src/js/features/notification.js
index 0ea7f93c8d..2b7fc45237 100644
--- a/web_src/js/features/notification.js
+++ b/web_src/js/features/notification.js
@@ -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');
+ // 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 {
- notificationCount.removeClass('hidden');
+ return;
}
-
- 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;
+ });
+ worker.port.addEventListener('error', (e) => {
+ console.error(e);
+ });
+ worker.port.start();
+ window.addEventListener('beforeunload', () => {
+ worker.port.postMessage({
+ type: 'close',
+ });
+ worker.port.close();
+ });
+
+ return;
+ }
}
if (NotificationSettings.MinTimeout <= 0) {
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 0cfba4c66a..15ca32cfdc 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -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(),
]);
});