diff --git a/gulp/res/js/forms.js b/gulp/res/js/forms.js index 9e8f6ac5..8a7ed7ac 100644 --- a/gulp/res/js/forms.js +++ b/gulp/res/js/forms.js @@ -120,7 +120,8 @@ class formHandler { return window.location = json.redirect; } setLocalStorage('myPostId', json.postId); - window.location.reload(); + forceUpdate(); +// window.location.reload(); } } this.form.reset(); diff --git a/gulp/res/js/live.js b/gulp/res/js/live.js index afac32d7..feb945a8 100644 --- a/gulp/res/js/live.js +++ b/gulp/res/js/live.js @@ -1,30 +1,30 @@ setDefaultLocalStorage('live', true); setDefaultLocalStorage('notifications', false); setDefaultLocalStorage('scroll', false); - -var socket; +let liveEnabled = localStorage.getItem('live') == 'true'; +let notificationsEnabled = localStorage.getItem('notifications') == 'true'; +let scrollEnabled = localStorage.getItem('scroll') == 'true'; +let socket; +let forceUpdate; window.addEventListener('settingsReady', function(event) { //after domcontentloaded + let supportsWebSockets = 'WebSocket' in window || 'MozWebSocket' in window; const livecolor = document.getElementById('livecolor'); const livetext = isThread ? document.getElementById('livetext').childNodes[1] : null; const updateButton = livetext ? livetext.nextSibling : null; - const updateLive = (message, color) => { + const updateLive = (message, color, showRelativeTime) => { livecolor.style.backgroundColor = color; - livetext.nodeValue = message; + livetext.nodeValue = `${message}`; } - let lastPostId; + let liveTimeout; const anchors = document.getElementsByClassName('anchor'); - - let liveEnabled = localStorage.getItem('live') == 'true'; - let notificationsEnabled = localStorage.getItem('notifications') == 'true'; - let scrollEnabled = localStorage.getItem('scroll') == 'true'; - if (anchors.length > 0) { lastPostId = anchors[anchors.length - 1].id; } const thread = document.querySelector('.thread'); + const newPost = (data) => { console.log('got new post'); lastPostId = data.postId; @@ -83,111 +83,132 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa window.dispatchEvent(newPostEvent); } - const jsonPath = window.location.pathname.replace(/\.html$/, '.json'); - const jsonCatchup = async () => { - console.log('catching up after reconnect'); + const fetchNewPosts = async () => { + console.log('fetching posts from api'); updateLive('Fetching posts...', 'yellow'); let json; + let newPosts = []; try { json = await fetch(jsonPath).then(res => res.json()); } catch (e) { console.error(e); } if (json && json.replies && json.replies.length > 0) { - const newPosts = json.replies.filter(r => r.postId > lastPostId); //filter to only newer posts + newPosts = json.replies.filter(r => r.postId > lastPostId); //filter to only newer posts if (newPosts.length > 0) { for (let i = 0; i < newPosts.length; i++) { newPost(newPosts[i]); } } } + updateLive('Updated', 'green'); + return newPosts.length; } - const startLive = () => { - const roomParts = window.location.pathname.replace(/\.html$/, '').split('/'); - const room = `${roomParts[1]}-${roomParts[3]}`; - socket = io({ transports: ['websocket'] }); - socket.on('connect', () => { - updateButton.style.display = 'none'; - console.log('joined room', room); - updateLive('Connected for live posts', '#0de600'); - socket.emit('room', room); - }); - socket.on('pong', (latency) => { - updateButton.style.display = 'none'; - if (socket.connected) { - updateLive(`Connected for live posts (${latency}ms)`, '#0de600'); - } - }); - socket.on('reconnect_attempt', () => { - updateLive('Attempting to reconnect...', 'yellow'); - }); - socket.on('disconnect', () => { - updateButton.removeAttribute('style'); - console.log('lost connection to room'); - updateLive('Disconnected', 'red'); - }); - socket.on('reconnect', () => { + let interval = 5000; + forceUpdate = async () => { + updateButton.disabled = true; + clearTimeout(liveTimeout); + if ((await fetchNewPosts()) > 0) { + interval = 5000; + } else { + interval = Math.min(interval*2, 90000); + } + setTimeout(() => { + updateButton.disabled = false; + }, 10000); + if (liveEnabled) { + liveTimeout = setTimeout(forceUpdate, interval); + } + } + + const enableLive = () => { + if (supportsWebSockets) { updateButton.style.display = 'none'; - console.log('reconnected to room'); - jsonCatchup(); - }); - socket.on('error', (e) => { - updateButton.removeAttribute('style'); - updateLive('Socket error', 'orange'); - console.error(e); - }); - socket.on('connect_error', (e) => { - updateButton.removeAttribute('style'); - updateLive('Error connecting', 'orange'); - console.error(e); - }); - socket.on('reconnect_error', (e) => { + const roomParts = window.location.pathname.replace(/\.html$/, '').split('/'); + const room = `${roomParts[1]}-${roomParts[3]}`; + socket = io({ + transports: ['websocket'], + reconnectionAttempts: 5 + }); + socket.on('connect', async () => { + console.log('socket connected'); + await fetchNewPosts(); + socket.emit('room', room); + }); + socket.on('message', (message) => { + console.log(message, room); + if (message === 'joined') { + updateLive('Connected for live posts', '#0de600'); + } + }); + socket.on('pong', (latency) => { + if (socket.connected) { + updateLive(`Connected for live posts (${latency}ms)`, '#0de600'); + } + }); + socket.on('reconnect_attempt', () => { + updateLive('Attempting to reconnect...', 'yellow'); + }); + socket.on('disconnect', () => { + console.log('lost connection to room'); + updateLive('Disconnected', 'red'); + }); + socket.on('reconnect', () => { + console.log('reconnected to room'); + fetchNewPosts(); + }); + socket.on('error', (e) => { + updateLive('Socket error', 'orange'); + console.error(e); + }); + socket.on('connect_error', (e) => { + updateLive('Error connecting', 'orange'); + console.error(e); + }); + socket.on('reconnect_error', (e) => { + updateLive('Error reconnecting', 'orange'); + console.error(e); + }); + socket.on('reconnect_failed', (e) => { + updateLive('Failed reconnecting', 'orange'); + console.error(e); + console.log('failed to reconnnect, falling back to polling') + socket.close(); + supportsWebSockets = false; + enableLive(); + + }); + socket.on('newPost', newPost); + } else { + //websocket not supported, update with polling to api updateButton.removeAttribute('style'); - updateLive('Error reconnecting', 'orange'); - console.error(e); - }); - socket.on('newPost', newPost); - } + forceUpdate(); + } + }; - const liveSetting = document.getElementById('live-setting'); - const notificationSetting = document.getElementById('notification-setting'); - const scrollSetting = document.getElementById('scroll-setting'); + const disableLive = () => { + updateButton.removeAttribute('style'); + clearTimeout(liveTimeout); + if (socket && supportsWebSockets) { + socket.disconnect(); + } + updateLive('Live posts off', 'darkgray'); + }; + const liveSetting = document.getElementById('live-setting'); const toggleLive = () => { - if (isThread) { - if (socket && liveEnabled) { - socket.disconnect(); - updateLive('Live posts disabled', 'red'); - } else { - if (!socket) { - startLive(); - } else { - socket.connect(); - } - jsonCatchup(); - } - } liveEnabled = !liveEnabled; - if (!liveEnabled) { - //disable notifications and scroll when live off because they wont work - if (notificationsEnabled) { - notificationSetting.checked = false; - toggleNotifications(null, true); - } - if (scrollEnabled) { - scrollSetting.checked = false; - toggleScroll(null, true); - } - } + liveEnabled ? enableLive() : disableLive(); console.log('toggling live posts', liveEnabled); setLocalStorage('live', liveEnabled); } liveSetting.checked = liveEnabled; liveSetting.addEventListener('change', toggleLive, false); - const toggleNotifications = async (change, changeFromConflict) => { + const notificationSetting = document.getElementById('notification-setting'); + const toggleNotifications = async () => { notificationsEnabled = !notificationsEnabled; if (notificationsEnabled) { const result = await Notification.requestPermission() @@ -200,42 +221,22 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa } console.log('toggling notifications', notificationsEnabled); setLocalStorage('notifications', notificationsEnabled); - if (!liveEnabled && !changeFromConflict) { - liveSetting.checked = true; - toggleLive(); - } } notificationSetting.checked = notificationsEnabled; notificationSetting.addEventListener('change', toggleNotifications, false); - const toggleScroll = (change, changeFromConflict) => { + const scrollSetting = document.getElementById('scroll-setting'); + const toggleScroll = () => { scrollEnabled = !scrollEnabled; console.log('toggling post scrolling', scrollEnabled); setLocalStorage('scroll', scrollEnabled); - if (!liveEnabled && !changeFromConflict) { - liveSetting.checked = true; - toggleLive(); - } } scrollSetting.checked = scrollEnabled; scrollSetting.addEventListener('change', toggleScroll, false); if (isThread) { - const forceUpdate = async () => { - updateButton.disabled = true; - await jsonCatchup(); - updateLive('Updated', 'green'); - setTimeout(() => { - updateButton.disabled = false; - }, 10000); - } updateButton.addEventListener('click', forceUpdate); - if (liveEnabled) { - updateButton.style.display = 'none'; - startLive(); - } else { - updateLive('Live posts disabled', 'red'); - } + liveEnabled ? enableLive() : disableLive(); } }); diff --git a/socketio.js b/socketio.js index 0e382a3a..cebc21d3 100644 --- a/socketio.js +++ b/socketio.js @@ -19,6 +19,7 @@ module.exports = { socket.on('room', room => { //TODO: add some validation here that rooms exist or AT LEAST a regex for valid thread rooms socket.join(room); + socket.send('joined'); }); }); },