Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the JS error "EventSource is not defined" caused by some non-standard browsers (#20584) #20663

Merged
merged 1 commit into from
Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions web_src/js/features/eventsource.sharedworker.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ class Source {
self.addEventListener('connect', (e) => {
for (const port of e.ports) {
port.addEventListener('message', (event) => {
if (!self.EventSource) {
// some browsers (like PaleMoon, Firefox<53) don't support EventSource in SharedWorkerGlobalScope.
// this event handler needs EventSource when doing "new Source(url)", so just post a message back to the caller,
// in case the caller would like to use a fallback method to do its work.
port.postMessage({type: 'no-event-source'});
return;
}
if (event.data.type === 'start') {
const url = event.data.url;
if (sourcesByUrl[url]) {
Expand Down
37 changes: 20 additions & 17 deletions web_src/js/features/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,41 @@ export function initNotificationCount() {
return;
}

if (notificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
let usingPeriodicPoller = false;
const startPeriodicPoller = (timeout, lastCount) => {
if (timeout <= 0 || !Number.isFinite(timeout)) return;
usingPeriodicPoller = true;
lastCount = lastCount ?? notificationCount.text();
setTimeout(async () => {
await updateNotificationCountWithCallback(startPeriodicPoller, timeout, lastCount);
}, timeout);
};

if (notificationSettings.EventSourceUpdateTime > 0 && window.EventSource && window.SharedWorker) {
// Try to connect to the event source via the shared worker first
const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
worker.addEventListener('error', (event) => {
console.error(event);
console.error('worker error', event);
});
worker.port.addEventListener('messageerror', () => {
console.error('Unable to deserialize message');
console.error('unable to deserialize message');
});
worker.port.postMessage({
type: 'start',
url: `${window.location.origin}${appSubUrl}/user/events`,
});
worker.port.addEventListener('message', (event) => {
if (!event.data || !event.data.type) {
console.error(event);
console.error('unknown worker message event', event);
return;
}
if (event.data.type === 'notification-count') {
const _promise = receiveUpdateCount(event.data);
} else if (event.data.type === 'no-event-source') {
// browser doesn't support EventSource, falling back to periodic poller
if (!usingPeriodicPoller) startPeriodicPoller(notificationSettings.MinTimeout);
} else if (event.data.type === 'error') {
console.error(event.data);
console.error('worker port event error', event.data);
} else if (event.data.type === 'logout') {
if (event.data.data !== 'here') {
return;
Expand All @@ -86,7 +99,7 @@ export function initNotificationCount() {
}
});
worker.port.addEventListener('error', (e) => {
console.error(e);
console.error('worker port error', e);
});
worker.port.start();
window.addEventListener('beforeunload', () => {
Expand All @@ -99,17 +112,7 @@ export function initNotificationCount() {
return;
}

if (notificationSettings.MinTimeout <= 0) {
return;
}

const fn = (timeout, lastCount) => {
setTimeout(() => {
const _promise = updateNotificationCountWithCallback(fn, timeout, lastCount);
}, timeout);
};

fn(notificationSettings.MinTimeout, notificationCount.text());
startPeriodicPoller(notificationSettings.MinTimeout);
}

async function updateNotificationCountWithCallback(callback, timeout, lastCount) {
Expand Down
77 changes: 39 additions & 38 deletions web_src/js/features/stopwatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import $ from 'jquery';
import prettyMilliseconds from 'pretty-ms';

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

export function initStopwatch() {
if (!enableTimeTracking) {
Expand All @@ -26,28 +25,45 @@ export function initStopwatch() {
$(this).parent().trigger('submit');
});

if (notificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource && window.SharedWorker) {
// global stop watch (in the head_navbar), it should always work in any case either the EventSource or the PeriodicPoller is used.
const currSeconds = $('.stopwatch-time').attr('data-seconds');
if (currSeconds) {
updateStopwatchTime(currSeconds);
}

let usingPeriodicPoller = false;
const startPeriodicPoller = (timeout) => {
if (timeout <= 0 || !Number.isFinite(timeout)) return;
usingPeriodicPoller = true;
setTimeout(() => updateStopwatchWithCallback(startPeriodicPoller, timeout), timeout);
};

// if the browser supports EventSource and SharedWorker, use it instead of the periodic poller
if (notificationSettings.EventSourceUpdateTime > 0 && window.EventSource && window.SharedWorker) {
// Try to connect to the event source via the shared worker first
const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js`, 'notification-worker');
worker.addEventListener('error', (event) => {
console.error(event);
console.error('worker error', event);
});
worker.port.addEventListener('messageerror', () => {
console.error('Unable to deserialize message');
console.error('unable to deserialize message');
});
worker.port.postMessage({
type: 'start',
url: `${window.location.origin}${appSubUrl}/user/events`,
});
worker.port.addEventListener('message', (event) => {
if (!event.data || !event.data.type) {
console.error(event);
console.error('unknown worker message event', event);
return;
}
if (event.data.type === 'stopwatches') {
updateStopwatchData(JSON.parse(event.data.data));
} else if (event.data.type === 'no-event-source') {
// browser doesn't support EventSource, falling back to periodic poller
if (!usingPeriodicPoller) startPeriodicPoller(notificationSettings.MinTimeout);
} else if (event.data.type === 'error') {
console.error(event.data);
console.error('worker port event error', event.data);
} else if (event.data.type === 'logout') {
if (event.data.data !== 'here') {
return;
Expand All @@ -65,7 +81,7 @@ export function initStopwatch() {
}
});
worker.port.addEventListener('error', (e) => {
console.error(e);
console.error('worker port error', e);
});
worker.port.start();
window.addEventListener('beforeunload', () => {
Expand All @@ -78,22 +94,7 @@ export function initStopwatch() {
return;
}

if (notificationSettings.MinTimeout <= 0) {
return;
}

const fn = (timeout) => {
setTimeout(() => {
const _promise = updateStopwatchWithCallback(fn, timeout);
}, timeout);
};

fn(notificationSettings.MinTimeout);

const currSeconds = $('.stopwatch-time').data('seconds');
if (currSeconds) {
updateTimeInterval = updateStopwatchTime(currSeconds);
}
startPeriodicPoller(notificationSettings.MinTimeout);
}

async function updateStopwatchWithCallback(callback, timeout) {
Expand All @@ -114,23 +115,14 @@ async function updateStopwatch() {
url: `${appSubUrl}/user/stopwatches`,
headers: {'X-Csrf-Token': csrfToken},
});

if (updateTimeInterval) {
clearInterval(updateTimeInterval);
updateTimeInterval = null;
}

return updateStopwatchData(data);
}

function updateStopwatchData(data) {
const watch = data[0];
const btnEl = $('.active-stopwatch-trigger');
if (!watch) {
if (updateTimeInterval) {
clearInterval(updateTimeInterval);
updateTimeInterval = null;
}
clearStopwatchTimer();
btnEl.addClass('hidden');
} else {
const {repo_owner_name, repo_name, issue_index, seconds} = watch;
Expand All @@ -139,22 +131,31 @@ function updateStopwatchData(data) {
$('.stopwatch-commit').attr('action', `${issueUrl}/times/stopwatch/toggle`);
$('.stopwatch-cancel').attr('action', `${issueUrl}/times/stopwatch/cancel`);
$('.stopwatch-issue').text(`${repo_owner_name}/${repo_name}#${issue_index}`);
$('.stopwatch-time').text(prettyMilliseconds(seconds * 1000));
updateStopwatchTime(seconds);
btnEl.removeClass('hidden');
}

return !!data.length;
}

let updateTimeIntervalId = null; // holds setInterval id when active
function clearStopwatchTimer() {
if (updateTimeIntervalId !== null) {
clearInterval(updateTimeIntervalId);
updateTimeIntervalId = null;
}
}
function updateStopwatchTime(seconds) {
const secs = parseInt(seconds);
if (!Number.isFinite(secs)) return;

clearStopwatchTimer();
const $stopwatch = $('.stopwatch-time');
const start = Date.now();
updateTimeInterval = setInterval(() => {
const updateUi = () => {
const delta = Date.now() - start;
const dur = prettyMilliseconds(secs * 1000 + delta, {compact: true});
$('.stopwatch-time').text(dur);
}, 1000);
$stopwatch.text(dur);
};
updateUi();
updateTimeIntervalId = setInterval(updateUi, 1000);
}