Skip to content

Commit 5ce8fdb

Browse files
authored
Fix the JS error "EventSource is not defined" caused by some non-standard browsers (#20584) (#20663)
1 parent 76accb5 commit 5ce8fdb

File tree

3 files changed

+66
-55
lines changed

3 files changed

+66
-55
lines changed

web_src/js/features/eventsource.sharedworker.js

+7
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

+20-17
Original file line numberDiff line numberDiff line change
@@ -47,28 +47,41 @@ export function initNotificationCount() {
4747
return;
4848
}
4949

50-
if (notificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
50+
let usingPeriodicPoller = false;
51+
const startPeriodicPoller = (timeout, lastCount) => {
52+
if (timeout <= 0 || !Number.isFinite(timeout)) return;
53+
usingPeriodicPoller = true;
54+
lastCount = lastCount ?? notificationCount.text();
55+
setTimeout(async () => {
56+
await updateNotificationCountWithCallback(startPeriodicPoller, timeout, lastCount);
57+
}, timeout);
58+
};
59+
60+
if (notificationSettings.EventSourceUpdateTime > 0 && window.EventSource && window.SharedWorker) {
5161
// Try to connect to the event source via the shared worker first
5262
const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
5363
worker.addEventListener('error', (event) => {
54-
console.error(event);
64+
console.error('worker error', event);
5565
});
5666
worker.port.addEventListener('messageerror', () => {
57-
console.error('Unable to deserialize message');
67+
console.error('unable to deserialize message');
5868
});
5969
worker.port.postMessage({
6070
type: 'start',
6171
url: `${window.location.origin}${appSubUrl}/user/events`,
6272
});
6373
worker.port.addEventListener('message', (event) => {
6474
if (!event.data || !event.data.type) {
65-
console.error(event);
75+
console.error('unknown worker message event', event);
6676
return;
6777
}
6878
if (event.data.type === 'notification-count') {
6979
const _promise = receiveUpdateCount(event.data);
80+
} else if (event.data.type === 'no-event-source') {
81+
// browser doesn't support EventSource, falling back to periodic poller
82+
if (!usingPeriodicPoller) startPeriodicPoller(notificationSettings.MinTimeout);
7083
} else if (event.data.type === 'error') {
71-
console.error(event.data);
84+
console.error('worker port event error', event.data);
7285
} else if (event.data.type === 'logout') {
7386
if (event.data.data !== 'here') {
7487
return;
@@ -86,7 +99,7 @@ export function initNotificationCount() {
8699
}
87100
});
88101
worker.port.addEventListener('error', (e) => {
89-
console.error(e);
102+
console.error('worker port error', e);
90103
});
91104
worker.port.start();
92105
window.addEventListener('beforeunload', () => {
@@ -99,17 +112,7 @@ export function initNotificationCount() {
99112
return;
100113
}
101114

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

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

web_src/js/features/stopwatch.js

+39-38
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));
143134
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);
152149
if (!Number.isFinite(secs)) return;
153150

151+
clearStopwatchTimer();
152+
const $stopwatch = $('.stopwatch-time');
154153
const start = Date.now();
155-
updateTimeInterval = 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)