Skip to content

Commit 7b5b616

Browse files
committed
still partial
Signed-off-by: Andrew Thornton <[email protected]>
1 parent b954177 commit 7b5b616

File tree

7 files changed

+246
-15
lines changed

7 files changed

+246
-15
lines changed

modules/context/response.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,29 @@ func (r *Response) Before(f func(ResponseWriter)) {
8484
r.befores = append(r.befores, f)
8585
}
8686

87+
type hijackerResponse struct {
88+
*Response
89+
http.Hijacker
90+
}
91+
8792
// NewResponse creates a response
88-
func NewResponse(resp http.ResponseWriter) *Response {
93+
func NewResponse(resp http.ResponseWriter) ResponseWriter {
8994
if v, ok := resp.(*Response); ok {
9095
return v
9196
}
92-
return &Response{
97+
hijacker, ok := resp.(http.Hijacker)
98+
99+
response := &Response{
93100
ResponseWriter: resp,
94101
status: 0,
95102
befores: make([]func(ResponseWriter), 0),
96103
}
104+
if ok {
105+
return hijackerResponse{
106+
Response: response,
107+
Hijacker: hijacker,
108+
}
109+
}
110+
111+
return response
97112
}

routers/web/events/websocket.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
package events
66

77
import (
8+
"net/http"
9+
"net/url"
10+
811
"code.gitea.io/gitea/modules/context"
912
"code.gitea.io/gitea/modules/eventsource"
1013
"code.gitea.io/gitea/modules/graceful"
1114
"code.gitea.io/gitea/modules/json"
1215
"code.gitea.io/gitea/modules/log"
16+
"code.gitea.io/gitea/modules/setting"
1317
"code.gitea.io/gitea/routers/web/auth"
1418
"github.com/gorilla/websocket"
1519
)
@@ -25,8 +29,28 @@ func Websocket(ctx *context.Context) {
2529
upgrader := websocket.Upgrader{
2630
ReadBufferSize: 1024,
2731
WriteBufferSize: 1024,
32+
CheckOrigin: func(r *http.Request) bool {
33+
origin := r.Header["Origin"]
34+
if len(origin) == 0 {
35+
return true
36+
}
37+
u, err := url.Parse(origin[0])
38+
if err != nil {
39+
return false
40+
}
41+
appURLURL, err := url.Parse(setting.AppURL)
42+
if err != nil {
43+
return true
44+
}
45+
46+
return u.Host == appURLURL.Host
47+
},
2848
}
2949

50+
// Because http proxies will tend not to pass these headers
51+
ctx.Req.Header.Add("Upgrade", "websocket")
52+
ctx.Req.Header.Add("Connection", "Upgrade")
53+
3054
conn, err := upgrader.Upgrade(ctx.Resp, ctx.Req, nil)
3155
if err != nil {
3256
log.Error("Unable to upgrade due to error: %v", err)

web_src/js/features/eventsource.sharedworker.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,13 @@ class Source {
4646
if (this.listening[eventType]) return;
4747
this.listening[eventType] = true;
4848
this.eventSource.addEventListener(eventType, (event) => {
49+
let data;
50+
if (event.data) {
51+
data = JSON.parse(event.data);
52+
}
4953
this.notifyClients({
5054
type: eventType,
51-
data: event.data
55+
data
5256
});
5357
});
5458
}

web_src/js/features/notification.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ export function initNotificationsTable() {
2424
});
2525
}
2626

27-
async function receiveUpdateCount(event) {
27+
async function receiveUpdateCount(data) {
28+
console.log(data);
2829
try {
29-
const data = JSON.parse(event.data);
30-
3130
const notificationCount = document.querySelector('.notification_count');
3231
if (data.Count > 0) {
3332
notificationCount.classList.remove('hidden');
@@ -36,9 +35,10 @@ async function receiveUpdateCount(event) {
3635
}
3736

3837
notificationCount.textContent = `${data.Count}`;
38+
console.log(notificationCount);
3939
await updateNotificationTable();
4040
} catch (error) {
41-
console.error(error, event);
41+
console.error(error, data);
4242
}
4343
}
4444

@@ -49,9 +49,20 @@ export function initNotificationCount() {
4949
return;
5050
}
5151

52-
if (notificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
52+
let worker;
53+
let workerUrl;
54+
55+
if (notificationSettings.EventSourceUpdateTime > 0 && !!window.WebSocket && window.SharedWorker) {
56+
// Try to connect to the event source via the shared worker first
57+
worker = new SharedWorker(`${__webpack_public_path__}js/websocket.sharedworker.js`, 'notification-worker');
58+
workerUrl = `${window.location.origin}${appSubUrl}/user/websocket`;
59+
} else if (notificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
5360
// Try to connect to the event source via the shared worker first
54-
const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
61+
worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
62+
workerUrl = `${window.location.origin}${appSubUrl}/user/events`;
63+
}
64+
65+
if (worker) {
5566
worker.addEventListener('error', (event) => {
5667
console.error(event);
5768
});
@@ -60,15 +71,16 @@ export function initNotificationCount() {
6071
});
6172
worker.port.postMessage({
6273
type: 'start',
63-
url: `${window.location.origin}${appSubUrl}/user/events`,
74+
url: workerUrl,
6475
});
6576
worker.port.addEventListener('message', (event) => {
6677
if (!event.data || !event.data.type) {
6778
console.error(event);
6879
return;
6980
}
81+
console.log(event);
7082
if (event.data.type === 'notification-count') {
71-
const _promise = receiveUpdateCount(event.data);
83+
const _promise = receiveUpdateCount(event.data.data).then(console.log('done'));
7284
} else if (event.data.type === 'error') {
7385
console.error(event.data);
7486
} else if (event.data.type === 'logout') {

web_src/js/features/stopwatch.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,19 @@ export function initStopwatch() {
2626
$(this).parent().trigger('submit');
2727
});
2828

29-
if (notificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
29+
let worker;
30+
let workerUrl;
31+
if (notificationSettings.EventSourceUpdateTime > 0 && !!window.WebSocket && window.SharedWorker) {
3032
// Try to connect to the event source via the shared worker first
31-
const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
33+
worker = new SharedWorker(`${__webpack_public_path__}js/websocket.sharedworker.js`, 'notification-worker');
34+
workerUrl = `${window.location.origin}${appSubUrl}/user/websocket`;
35+
} else if (notificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
36+
// Try to connect to the event source via the shared worker first
37+
worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
38+
workerUrl = `${window.location.origin}${appSubUrl}/user/events`;
39+
}
40+
41+
if (worker) {
3242
worker.addEventListener('error', (event) => {
3343
console.error(event);
3444
});
@@ -37,15 +47,15 @@ export function initStopwatch() {
3747
});
3848
worker.port.postMessage({
3949
type: 'start',
40-
url: `${window.location.origin}${appSubUrl}/user/events`,
50+
url: workerUrl,
4151
});
4252
worker.port.addEventListener('message', (event) => {
4353
if (!event.data || !event.data.type) {
4454
console.error(event);
4555
return;
4656
}
4757
if (event.data.type === 'stopwatches') {
48-
updateStopwatchData(JSON.parse(event.data.data));
58+
updateStopwatchData(event.data.data);
4959
} else if (event.data.type === 'error') {
5060
console.error(event.data);
5161
} else if (event.data.type === 'logout') {
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
const sourcesByUrl = {};
2+
const sourcesByPort = {};
3+
4+
class Source {
5+
constructor(url) {
6+
this.url = url.replace(/^http/, 'ws');
7+
this.webSocket = new WebSocket(this.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+
this.webSocket.addEventListener('error', (error) => {
17+
this.lastError = error;
18+
});
19+
this.webSocket.addEventListener('message', (event) => {
20+
const message = JSON.parse(event.data);
21+
if (!message) {
22+
return;
23+
}
24+
if (this.listening[message.Name]) {
25+
this.notifyClients({
26+
type: message.Name,
27+
data: message.Data
28+
});
29+
}
30+
});
31+
}
32+
33+
register(port) {
34+
if (this.clients.includes(port)) return;
35+
36+
this.clients.push(port);
37+
38+
port.postMessage({
39+
type: 'status',
40+
message: `registered to ${this.url}`,
41+
});
42+
43+
if (!this.webSocket) {
44+
if (this.lastError) {
45+
port.postMessage({
46+
type: 'error',
47+
message: `websocket disconnected: ${this.lastError}`
48+
});
49+
} else {
50+
port.postMessage({
51+
type: 'error',
52+
message: 'websocket disconnected'
53+
});
54+
}
55+
}
56+
}
57+
58+
deregister(port) {
59+
const portIdx = this.clients.indexOf(port);
60+
if (portIdx < 0) {
61+
return this.clients.length;
62+
}
63+
this.clients.splice(portIdx, 1);
64+
return this.clients.length;
65+
}
66+
67+
close() {
68+
if (!this.webSocket) return;
69+
70+
this.webSocket.close();
71+
this.webSocket = null;
72+
}
73+
74+
listen(eventType) {
75+
if (this.listening[eventType]) return;
76+
this.listening[eventType] = true;
77+
this.webSocket.addEventListener(eventType, (event) => {
78+
this.notifyClients({
79+
type: eventType,
80+
data: event.data
81+
});
82+
});
83+
}
84+
85+
notifyClients(event) {
86+
for (const client of this.clients) {
87+
client.postMessage(event);
88+
}
89+
}
90+
91+
status(port) {
92+
port.postMessage({
93+
type: 'status',
94+
message: `url: ${this.url} readyState: ${this.webSocket.readyState}`,
95+
});
96+
}
97+
}
98+
99+
self.addEventListener('connect', (e) => {
100+
for (const port of e.ports) {
101+
port.addEventListener('message', (event) => {
102+
if (event.data.type === 'start') {
103+
const url = event.data.url;
104+
if (sourcesByUrl[url]) {
105+
// we have a Source registered to this url
106+
const source = sourcesByUrl[url];
107+
source.register(port);
108+
sourcesByPort[port] = source;
109+
return;
110+
}
111+
let source = sourcesByPort[port];
112+
if (source) {
113+
if (source.eventSource && source.url === url) return;
114+
115+
// How this has happened I don't understand...
116+
// deregister from that source
117+
const count = source.deregister(port);
118+
// Clean-up
119+
if (count === 0) {
120+
source.close();
121+
sourcesByUrl[source.url] = null;
122+
}
123+
}
124+
// Create a new Source
125+
source = new Source(url);
126+
source.register(port);
127+
sourcesByUrl[url] = source;
128+
sourcesByPort[port] = source;
129+
} else if (event.data.type === 'listen') {
130+
const source = sourcesByPort[port];
131+
source.listen(event.data.eventType);
132+
} else if (event.data.type === 'close') {
133+
const source = sourcesByPort[port];
134+
135+
if (!source) return;
136+
137+
const count = source.deregister(port);
138+
if (count === 0) {
139+
source.close();
140+
sourcesByUrl[source.url] = null;
141+
sourcesByPort[port] = null;
142+
}
143+
} else if (event.data.type === 'status') {
144+
const source = sourcesByPort[port];
145+
if (!source) {
146+
port.postMessage({
147+
type: 'status',
148+
message: 'not connected',
149+
});
150+
return;
151+
}
152+
source.status(port);
153+
} else {
154+
// just send it back
155+
port.postMessage({
156+
type: 'error',
157+
message: `received but don't know how to handle: ${event.data}`,
158+
});
159+
}
160+
});
161+
port.start();
162+
}
163+
});

webpack.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ export default {
6262
'eventsource.sharedworker': [
6363
fileURLToPath(new URL('web_src/js/features/eventsource.sharedworker.js', import.meta.url)),
6464
],
65+
'websocket.sharedworker': [
66+
fileURLToPath(new URL('web_src/js/features/websocket.sharedworker.js', import.meta.url)),
67+
],
6568
...themes,
6669
},
6770
devtool: false,

0 commit comments

Comments
 (0)