Skip to content

Commit c6d2b38

Browse files
wxiaoguangsilverwind
authored and
Sysoev, Vladimir
committed
Fix the JS error "EventSource is not defined" caused by some non-standard browsers (go-gitea#20584)
* fall back to periodic poller Co-authored-by: silverwind <[email protected]>
1 parent 6aad662 commit c6d2b38

File tree

3 files changed

+71
-64
lines changed

3 files changed

+71
-64
lines changed

web_src/js/features/eventsource.sharedworker.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ class Source {
7070
self.addEventListener('connect', (e) => {
7171
for (const port of e.ports) {
7272
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+
}
7380
if (event.data.type === 'start') {
7481
const url = event.data.url;
7582
if (sourcesByUrl[url]) {

web_src/js/features/notification.js

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,10 @@ async function receiveUpdateCount(event) {
2828
try {
2929
const data = JSON.parse(event.data);
3030

31-
const notificationCount = document.querySelector('.notification_count');
32-
if (data.Count > 0) {
33-
notificationCount.classList.remove('hidden');
34-
} else {
35-
notificationCount.classList.add('hidden');
31+
for (const count of document.querySelectorAll('.notification_count')) {
32+
count.classList.toggle('hidden', data.Count === 0);
33+
count.textContent = `${data.Count}`;
3634
}
37-
38-
notificationCount.textContent = `${data.Count}`;
3935
await updateNotificationTable();
4036
} catch (error) {
4137
console.error(error, event);
@@ -49,28 +45,41 @@ export function initNotificationCount() {
4945
return;
5046
}
5147

52-
if (notificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
48+
let usingPeriodicPoller = false;
49+
const startPeriodicPoller = (timeout, lastCount) => {
50+
if (timeout <= 0 || !Number.isFinite(timeout)) return;
51+
usingPeriodicPoller = true;
52+
lastCount = lastCount ?? notificationCount.text();
53+
setTimeout(async () => {
54+
await updateNotificationCountWithCallback(startPeriodicPoller, timeout, lastCount);
55+
}, timeout);
56+
};
57+
58+
if (notificationSettings.EventSourceUpdateTime > 0 && window.EventSource && window.SharedWorker) {
5359
// Try to connect to the event source via the shared worker first
5460
const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
5561
worker.addEventListener('error', (event) => {
56-
console.error(event);
62+
console.error('worker error', event);
5763
});
5864
worker.port.addEventListener('messageerror', () => {
59-
console.error('Unable to deserialize message');
65+
console.error('unable to deserialize message');
6066
});
6167
worker.port.postMessage({
6268
type: 'start',
6369
url: `${window.location.origin}${appSubUrl}/user/events`,
6470
});
6571
worker.port.addEventListener('message', (event) => {
6672
if (!event.data || !event.data.type) {
67-
console.error(event);
73+
console.error('unknown worker message event', event);
6874
return;
6975
}
7076
if (event.data.type === 'notification-count') {
7177
const _promise = receiveUpdateCount(event.data);
78+
} else if (event.data.type === 'no-event-source') {
79+
// browser doesn't support EventSource, falling back to periodic poller
80+
if (!usingPeriodicPoller) startPeriodicPoller(notificationSettings.MinTimeout);
7281
} else if (event.data.type === 'error') {
73-
console.error(event.data);
82+
console.error('worker port event error', event.data);
7483
} else if (event.data.type === 'logout') {
7584
if (event.data.data !== 'here') {
7685
return;
@@ -88,7 +97,7 @@ export function initNotificationCount() {
8897
}
8998
});
9099
worker.port.addEventListener('error', (e) => {
91-
console.error(e);
100+
console.error('worker port error', e);
92101
});
93102
worker.port.start();
94103
window.addEventListener('beforeunload', () => {
@@ -101,17 +110,7 @@ export function initNotificationCount() {
101110
return;
102111
}
103112

104-
if (notificationSettings.MinTimeout <= 0) {
105-
return;
106-
}
107-
108-
const fn = (timeout, lastCount) => {
109-
setTimeout(() => {
110-
const _promise = updateNotificationCountWithCallback(fn, timeout, lastCount);
111-
}, timeout);
112-
};
113-
114-
fn(notificationSettings.MinTimeout, notificationCount.text());
113+
startPeriodicPoller(notificationSettings.MinTimeout);
115114
}
116115

117116
async function updateNotificationCountWithCallback(callback, timeout, lastCount) {

web_src/js/features/stopwatch.js

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import $ from 'jquery';
22
import prettyMilliseconds from 'pretty-ms';
33

44
const {appSubUrl, csrfToken, notificationSettings, enableTimeTracking} = window.config;
5-
let updateTimeInterval = null; // holds setInterval id when active
65

76
export function initStopwatch() {
87
if (!enableTimeTracking) {
@@ -26,28 +25,45 @@ export function initStopwatch() {
2625
$(this).parent().trigger('submit');
2726
});
2827

29-
if (notificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
28+
// global stop watch (in the head_navbar), it should always work in any case either the EventSource or the PeriodicPoller is used.
29+
const currSeconds = $('.stopwatch-time').attr('data-seconds');
30+
if (currSeconds) {
31+
updateStopwatchTime(currSeconds);
32+
}
33+
34+
let usingPeriodicPoller = false;
35+
const startPeriodicPoller = (timeout) => {
36+
if (timeout <= 0 || !Number.isFinite(timeout)) return;
37+
usingPeriodicPoller = true;
38+
setTimeout(() => updateStopwatchWithCallback(startPeriodicPoller, timeout), timeout);
39+
};
40+
41+
// if the browser supports EventSource and SharedWorker, use it instead of the periodic poller
42+
if (notificationSettings.EventSourceUpdateTime > 0 && window.EventSource && window.SharedWorker) {
3043
// Try to connect to the event source via the shared worker first
3144
const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
3245
worker.addEventListener('error', (event) => {
33-
console.error(event);
46+
console.error('worker error', event);
3447
});
3548
worker.port.addEventListener('messageerror', () => {
36-
console.error('Unable to deserialize message');
49+
console.error('unable to deserialize message');
3750
});
3851
worker.port.postMessage({
3952
type: 'start',
4053
url: `${window.location.origin}${appSubUrl}/user/events`,
4154
});
4255
worker.port.addEventListener('message', (event) => {
4356
if (!event.data || !event.data.type) {
44-
console.error(event);
57+
console.error('unknown worker message event', event);
4558
return;
4659
}
4760
if (event.data.type === 'stopwatches') {
4861
updateStopwatchData(JSON.parse(event.data.data));
62+
} else if (event.data.type === 'no-event-source') {
63+
// browser doesn't support EventSource, falling back to periodic poller
64+
if (!usingPeriodicPoller) startPeriodicPoller(notificationSettings.MinTimeout);
4965
} else if (event.data.type === 'error') {
50-
console.error(event.data);
66+
console.error('worker port event error', event.data);
5167
} else if (event.data.type === 'logout') {
5268
if (event.data.data !== 'here') {
5369
return;
@@ -65,7 +81,7 @@ export function initStopwatch() {
6581
}
6682
});
6783
worker.port.addEventListener('error', (e) => {
68-
console.error(e);
84+
console.error('worker port error', e);
6985
});
7086
worker.port.start();
7187
window.addEventListener('beforeunload', () => {
@@ -78,22 +94,7 @@ export function initStopwatch() {
7894
return;
7995
}
8096

81-
if (notificationSettings.MinTimeout <= 0) {
82-
return;
83-
}
84-
85-
const fn = (timeout) => {
86-
setTimeout(() => {
87-
const _promise = updateStopwatchWithCallback(fn, timeout);
88-
}, timeout);
89-
};
90-
91-
fn(notificationSettings.MinTimeout);
92-
93-
const currSeconds = $('.stopwatch-time').data('seconds');
94-
if (currSeconds) {
95-
updateTimeInterval = updateStopwatchTime(currSeconds);
96-
}
97+
startPeriodicPoller(notificationSettings.MinTimeout);
9798
}
9899

99100
async function updateStopwatchWithCallback(callback, timeout) {
@@ -114,23 +115,14 @@ async function updateStopwatch() {
114115
url: `${appSubUrl}/user/stopwatches`,
115116
headers: {'X-Csrf-Token': csrfToken},
116117
});
117-
118-
if (updateTimeInterval) {
119-
clearInterval(updateTimeInterval);
120-
updateTimeInterval = null;
121-
}
122-
123118
return updateStopwatchData(data);
124119
}
125120

126121
function updateStopwatchData(data) {
127122
const watch = data[0];
128123
const btnEl = $('.active-stopwatch-trigger');
129124
if (!watch) {
130-
if (updateTimeInterval) {
131-
clearInterval(updateTimeInterval);
132-
updateTimeInterval = null;
133-
}
125+
clearStopwatchTimer();
134126
btnEl.addClass('hidden');
135127
} else {
136128
const {repo_owner_name, repo_name, issue_index, seconds} = watch;
@@ -139,22 +131,31 @@ function updateStopwatchData(data) {
139131
$('.stopwatch-commit').attr('action', `${issueUrl}/times/stopwatch/toggle`);
140132
$('.stopwatch-cancel').attr('action', `${issueUrl}/times/stopwatch/cancel`);
141133
$('.stopwatch-issue').text(`${repo_owner_name}/${repo_name}#${issue_index}`);
142-
$('.stopwatch-time').text(prettyMilliseconds(seconds * 1000));
143-
updateTimeInterval = updateStopwatchTime(seconds);
134+
updateStopwatchTime(seconds);
144135
btnEl.removeClass('hidden');
145136
}
146-
147137
return !!data.length;
148138
}
149139

140+
let updateTimeIntervalId = null; // holds setInterval id when active
141+
function clearStopwatchTimer() {
142+
if (updateTimeIntervalId !== null) {
143+
clearInterval(updateTimeIntervalId);
144+
updateTimeIntervalId = null;
145+
}
146+
}
150147
function updateStopwatchTime(seconds) {
151148
const secs = parseInt(seconds);
152-
if (!Number.isFinite(secs)) return null;
149+
if (!Number.isFinite(secs)) return;
153150

151+
clearStopwatchTimer();
152+
const $stopwatch = $('.stopwatch-time');
154153
const start = Date.now();
155-
return setInterval(() => {
154+
const updateUi = () => {
156155
const delta = Date.now() - start;
157156
const dur = prettyMilliseconds(secs * 1000 + delta, {compact: true});
158-
$('.stopwatch-time').text(dur);
159-
}, 1000);
157+
$stopwatch.text(dur);
158+
};
159+
updateUi();
160+
updateTimeIntervalId = setInterval(updateUi, 1000);
160161
}

0 commit comments

Comments
 (0)