Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ TIMEOUT_STEP = 10s
; This setting determines how often the db is queried to get the latest notification counts.
; If the browser client supports EventSource, it will be used in preference to polling notification.
EVENT_SOURCE_UPDATE_TIME = 10s
USE_SHARED_WORKER=true ; use a eventsource in shared worker
USE_WORKER=false ; use eventsource in worker
USE_PLAIN_EVENT_SOURCE=false ; allow direct use of event source outside of worker

[markdown]
; Render soft line breaks as hard line breaks, which means a single newline character between
Expand Down
4 changes: 3 additions & 1 deletion docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `MAX_TIMEOUT`: **60s**.
- `TIMEOUT_STEP`: **10s**.
- `EVENT_SOURCE_UPDATE_TIME`: **10s**: This setting determines how often the database is queried to update notification counts. If the browser client supports `EventSource`, it will be used in preference to polling notification endpoint.

- `USE_SHARED_WORKER`: **true**: If the client supports `SharedWorker` and the `EVENT_SOURCE_UPDATE_TIME` is greater than `0`. Use a `SharedWorker` `EventSource` in preference to polling.
- `USE_WORKER`: **false**: If the client supports `Worker` and the `EVENT_SOURCE_UPDATE_TIME` is greater than `0`. Use a `Worker` `EventSource` in preference to polling. (Disabled by default due to limited browser connections under HTTP/1.1.)
- `USE_PLAIN_EVENT_SOURCE`: **false**: If the client supports `EventSource` and the `EVENT_SOURCE_UPDATE_TIME` is greater than `0`. Use a `EventSource` in preference to polling. (Disabled by default due to limited browser connections under HTTP/1.1.)

## Markdown (`markdown`)

Expand Down
9 changes: 9 additions & 0 deletions modules/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ var (
TimeoutStep time.Duration
MaxTimeout time.Duration
EventSourceUpdateTime time.Duration
UseSharedWorker bool
UseWorker bool
UsePlainEventSource bool
} `ini:"ui.notification"`

Admin struct {
Expand Down Expand Up @@ -220,11 +223,17 @@ var (
TimeoutStep time.Duration
MaxTimeout time.Duration
EventSourceUpdateTime time.Duration
UseSharedWorker bool
UseWorker bool
UsePlainEventSource bool
}{
MinTimeout: 10 * time.Second,
TimeoutStep: 10 * time.Second,
MaxTimeout: 60 * time.Second,
EventSourceUpdateTime: 10 * time.Second,
UseSharedWorker: true,
UseWorker: false,
UsePlainEventSource: false,
},
Admin: struct {
UserPagingNum int
Expand Down
7 changes: 5 additions & 2 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,15 @@ func NewFuncMap() []template.FuncMap {
return ""
}
},
"NotificationSettings": func() map[string]int {
return map[string]int{
"NotificationSettings": func() map[string]interface{} {
return map[string]interface{}{
"MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
"TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
"MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),
"EventSourceUpdateTime": int(setting.UI.Notification.EventSourceUpdateTime / time.Millisecond),
"UseSharedWorker": setting.UI.Notification.UseSharedWorker,
"UseWorker": setting.UI.Notification.UseWorker,
"UsePlainEventSource": setting.UI.Notification.UsePlainEventSource,
}
},
"contain": func(s []int64, id int64) bool {
Expand Down
3 changes: 3 additions & 0 deletions templates/base/head.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
TimeoutStep: {{NotificationSettings.TimeoutStep}},
MaxTimeout: {{NotificationSettings.MaxTimeout}},
EventSourceUpdateTime: {{NotificationSettings.EventSourceUpdateTime}},
UseSharedWorker: {{NotificationSettings.UseSharedWorker}},
UseWorker: {{NotificationSettings.UseWorker}},
UsePlainEventSource: {{NotificationSettings.UsePlainEventSource}},
},
{{if .RequireTribute}}
tributeValues: [
Expand Down
151 changes: 151 additions & 0 deletions web_src/js/features/eventsource.sharedworker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
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) {
const portIdx = this.clients.indexOf(port);
if (portIdx > -1) {
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
}, false);
});
}

notifyClients(event) {
const len = this.clients.length;
for (let i = 0; i < len; i++) {
const port = this.clients[i];
port.postMessage(event);
}
}

status(port) {
port.postMessage({
type: 'status',
message: `url: ${this.url} readyState: ${this.eventSource.readyState}`,
});
}
}

self.onconnect = (e) => {
e.ports.forEach((port) => {
port.addEventListener('message', (event) => {
switch (event.data.type) {
case '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) {
// We have a valid source for this port...
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;
}
case 'listen': {
const source = sourcesByPort[port];
source.listen(event.data.eventType);
}
return;
case '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;
}
case 'status': {
const source = sourcesByPort[port];
if (!source) {
port.postMessage({
type: 'status',
message: 'not connected',
});
return;
}
source.status(port);
return;
}
default:
// just send it back
port.postMessage({
type: 'error',
message: `received but don't know how to handle: ${event.data}`,
});
return;
}
});
port.start();
});
};
29 changes: 29 additions & 0 deletions web_src/js/features/eventsource.worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
let eventSource;

const listening = {};

self.addEventListener('message', (event) => {
if (event.data.type === 'start') {
eventSource = new EventSource(event.data.url);
listen('open');
listen('error');
listen('notification-count');
this.listen('logout');
} else if (event.data.type === 'listen') {
listen(event.data.eventType);
} else if (event.data.type === 'close' && eventSource) {
eventSource.close();
eventSource = null;
}
}, false);

function listen (eventType) {
if (listening[eventType]) return;
listening[eventType] = true;
eventSource.addEventListener(eventType, (event) => {
self.postMessage({
type: eventType,
data: event.data
}, false);
});
}
Loading