diff --git a/.gitignore b/.gitignore index 6b3b199..8d6c4af 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ cpd.sh *test*.js backend/config.json node_modules +test *.pem *.txt *.log diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 3469752..0000000 --- a/TODO.md +++ /dev/null @@ -1,2 +0,0 @@ -# TODO -- Adjust lightmode.css to match darkmode.css diff --git a/backend/SQLQuery.js b/backend/SQLQuery.js index 95fff5b..d5b6db1 100644 --- a/backend/SQLQuery.js +++ b/backend/SQLQuery.js @@ -70,11 +70,8 @@ log4js.configure({ }); const logger = log4js.getLogger("SQL"); - let pool = undefined; //Connection pool to MySQL server -let aliveConnections = 0; // Int denoting number of unreleased connections. - /** * Creates connection pool to mysql server. Could have just initialized * pool on require but having to explicitly start connections helps to @@ -123,9 +120,6 @@ function fetchTables(streams, regular, whitelisted){ let toReturn = false; - // Will always add another alive connection at the start of each function. - aliveConnections++; - pool.getConnection(function(err, connection){ // If error occurs, connection doesn't exist, so no need to release. @@ -177,7 +171,6 @@ function fetchTables(streams, regular, whitelisted){ } connection.release(); - aliveConnections--; }); @@ -188,20 +181,26 @@ function fetchTables(streams, regular, whitelisted){ * Gets week, month, year, and overall accumulated times for each person. * @param {string} stream Channel id of streamer to fetch data for. * @param {string} viewerUsername Name of user to get stats for. + * @param {boolean} isWhitelisted Whether or not viewer is whitelisted. * @param {ServerResponse} res Server response object used to send payload * received from the MySQL server to the client. * @return {boolean} Returns true if error occurred. False otherwise. * @export */ -function fetchLongTable(channelId, viewerUsername, res){ +function fetchLongTable(channelId, viewerUsername, isWhitelisted, res){ let toReturn = false; // Label tables to identify - const regTable = sql.raw(channelId + _REGULAR_SUFFIX); const graphTable = sql.raw(channelId + _GRAPH_SUFFIX); + let table = undefined; + if(isWhitelisted){ + table = sql.raw(channelId + _WHITELIST_SUFFIX); + } + else{ + table = sql.raw(channelId + _REGULAR_SUFFIX); - aliveConnections++; + } pool.getConnection(function(err, connection){ @@ -214,7 +213,7 @@ function fetchLongTable(channelId, viewerUsername, res){ const responsePayload = {} connection.query("SELECT username, week, month, year, all_time FROM ? " + "WHERE username=?;", - [regTable, viewerUsername], function(error, results, fields){ + [table, viewerUsername], function(error, results, fields){ if(_assertError(error, connection, res)){ toReturn = true; @@ -242,7 +241,6 @@ function fetchLongTable(channelId, viewerUsername, res){ }); - aliveConnections--; connection.release(); }); @@ -267,7 +265,6 @@ function fetchPeriodTimes(channelId, period, res){ const regTable = sql.raw(channelId + _REGULAR_SUFFIX); const graphTable = sql.raw(channelId + _GRAPH_SUFFIX); const periodRaw = sql.raw(period); - aliveConnections++; pool.query("SELECT username, ? from ?;", [periodRaw, regTable], function(err, results, fields){ @@ -301,7 +298,6 @@ function addStreamerTable(channelId){ const channelIdRegular = sql.raw(channelId + _REGULAR_SUFFIX); const whitelistId = sql.raw(channelId + _WHITELIST_SUFFIX); - aliveConnections++; pool.getConnection(function(err, connection){ @@ -341,7 +337,6 @@ function addStreamerTable(channelId){ }); connection.release(); - aliveConnections--; }); @@ -374,15 +369,11 @@ function addViewer(channelId, viewerId, viewerUsername, times=[0, 0, 0, 0], else{ channelId = sql.raw(channelId + _REGULAR_SUFFIX); } - - aliveConnections++; pool.query("INSERT INTO ? VALUES (?, ?, ?);", [channelId, viewerId, viewerUsername, times], function(error){ - aliveConnections--; - if(_assertConnectionError(error)){ toReturn = true; return; @@ -413,16 +404,17 @@ function swapViewer(channelId, viewerUsername, whitelisted=false){ const regTable = sql.raw(channelId + _REGULAR_SUFFIX); const whitelistTable = sql.raw(channelId + _WHITELIST_SUFFIX); let removeFrom = undefined; + let insertInto = undefined; if(whitelisted){ removeFrom = whitelistTable; + insertInto = regTable; } else{ removeFrom = regTable; + insertInto = whitelistTable; } - aliveConnections++; - pool.getConnection(function(err, connection){ if(_assertConnectionError(err)){ @@ -433,26 +425,37 @@ function swapViewer(channelId, viewerUsername, whitelisted=false){ // Initialize array to insert all times in. const times = []; let viewerId = undefined; - connection.query("SELECT * FROM ? WHERE viewerUsername=?;", + connection.query("SELECT * FROM ? WHERE username=?;", [removeFrom, viewerUsername], function(error, results, fields){ - - if(_assertError(error, connection)){ - toReturn = true; - return; - } + + if(_assertError(error, connection)){ + toReturn = true; + return; + } + + const row = results[0]; + + // Insert all times in times array. + times.push(row.week); + times.push(row.month); + times.push(row.year); + times.push(row.all_time); + viewerId = row.id; - const row = results[0]; + // Insert into new table. + connection.query("INSERT INTO ? VALUES (?, ?, ?);", + [insertInto, viewerId, viewerUsername, times], + function(error){ - // Insert all times in times array. - times.push(row.week); - times.push(row.month); - times.push(row.year); - times.push(row.all_time); - viewerId = row.id; + if(_assertError(error, connection)){ + toReturn = true; + return; + } + }); }); // Remove from previous table. - connection.query("DELETE FROM ? WHERE viewerUsername=?;", + connection.query("DELETE FROM ? WHERE username=?;", [removeFrom, viewerUsername], function(error){ if(_assertError(error, connection)){ @@ -461,18 +464,8 @@ function swapViewer(channelId, viewerUsername, whitelisted=false){ } }); - // Insert into new table. - connection.query("INSERT INTO ? VALUES (?, ?, ?);", - [viewerId, viewerUsername, times], function(error){ - - if(_assertError(error, connection)){ - toReturn = true; - return; - } - }); connection.release(); - aliveConnections--; }); return toReturn; @@ -489,21 +482,17 @@ function createStreamerList(){ let toReturn = false; - aliveConnections++; - pool.query("CREATE TABLE list_of_streamers(channel_id VARCHAR(50), " + "PRIMARY KEY(channel_id));", function(error){ if(error && error.message == json.tableExists){ - aliveConnections--; return; } else if(_assertConnectionError(error)){ toReturn = true; return; } - aliveConnections--; }); @@ -521,8 +510,6 @@ function updateStreamerList(channelId){ let toReturn = false; - aliveConnections++; - const query = "INSERT INTO list_of_streamers VALUES (?);"; const args = [channelId]; @@ -533,7 +520,6 @@ function updateStreamerList(channelId){ toReturn = true; return; } - aliveConnections--; }); @@ -552,8 +538,6 @@ function fetchStreamerList(callback, ...args){ let toReturn = false; - aliveConnections++; - pool.query("SELECT channel_id FROM list_of_streamers;", function(error, results, fields){ @@ -562,7 +546,6 @@ function fetchStreamerList(callback, ...args){ toReturn = true; return; } - aliveConnections--; // Begin populating array with results from query const toPopulate = []; @@ -591,7 +574,6 @@ function createGraphTable(channelId){ let toReturn = false; channelId = sql.raw(channelId + _GRAPH_SUFFIX); - aliveConnections++; pool.query("CREATE TABLE ?(id VARCHAR(50) NOT NULL UNIQUE, " + "username VARCHAR(50) NOT NULL UNIQUE, PRIMARY KEY(username));", @@ -602,7 +584,6 @@ function createGraphTable(channelId){ toReturn = true; return; } - aliveConnections--; }); return toReturn; @@ -629,7 +610,6 @@ function updateGraphTable(channelId, times){ `${today.getMonth()}_${today.getDate()}_${today.getFullYear()}`); channelId = sql.raw(channelId + _GRAPH_SUFFIX); - aliveConnections++; pool.getConnection(function(err, connection){ @@ -664,7 +644,6 @@ function updateGraphTable(channelId, times){ }); } - aliveConnections--; connection.release(); }); @@ -687,7 +666,6 @@ function addViewerGraphTable(channelId, viewerId, viewerUsername){ let toReturn = false; channelId = sql.raw(channelId + _GRAPH_SUFFIX); - aliveConnections++; pool.query("INSERT INTO ? (id, username) VALUES (?, ?);", [channelId, viewerId, viewerUsername], function(err){ @@ -697,7 +675,6 @@ function addViewerGraphTable(channelId, viewerId, viewerUsername){ toReturn = true; return; } - aliveConnections--; }); return toReturn; @@ -710,8 +687,6 @@ function addViewerGraphTable(channelId, viewerId, viewerUsername){ */ function clearWeek(){ - aliveConnections++; - pool.getConnection(function(err, connection){ if(_assertConnectionError(err)){ @@ -737,7 +712,7 @@ function clearWeek(){ }); connection.query("UPDATE ?, ? SET week=0;", - [streamerReg, streamerWhitelist], function(error){ + [streamersReg, streamerWhitelist], function(error){ if(_assertError(error)){ return; @@ -745,7 +720,6 @@ function clearWeek(){ }); - aliveConnections--; connection.release(); }); @@ -758,8 +732,6 @@ function clearWeek(){ */ function clearMonth(){ - aliveConnections++; - pool.getConnection(function(err, connection){ if(_assertConnectionError(err)){ @@ -785,7 +757,7 @@ function clearMonth(){ }); connection.query("UPDATE ?, ? SET month=0;", - [streamerReg, streamerWhitelist], function(error){ + [streamersReg, streamerWhitelist], function(error){ if(_assertError(error)){ return; @@ -793,7 +765,6 @@ function clearMonth(){ }); - aliveConnections--; connection.release(); }); @@ -818,8 +789,6 @@ function updateTime(regular, whitelisted){ let toReturn = false; - aliveConnections++; - pool.getConnection(function(err, connection){ if(_assertConnectionError(err)){ @@ -888,7 +857,6 @@ function updateTime(regular, whitelisted){ } connection.release(); - aliveConnections--; }); @@ -901,32 +869,21 @@ function updateTime(regular, whitelisted){ * @export */ function endConnections(){ - - // Periodically check if there are any more alive connections. - let wait = setInterval(function(){ - - // Once all connections are released clear the interval and end the - // pool. - if(aliveConnections == 0){ - if(pool == undefined){ - clearInterval(wait); - log4js.shutdown(function(){}); - } + if(pool == undefined){ + log4js.shutdown(function(){}); + return; + } - pool.end(function(err){ + pool.end(function(err){ - clearInterval(wait); - - if(err){ - logger.error(error.message); - } + if(err){ + logger.error(error.message); + } - log4js.shutdown(function(){}); + log4js.shutdown(function(){}); - }); - } - }, json.exitCheck); + }); } /** @@ -959,7 +916,7 @@ function _parsePeriodTimes(res, period){ */ function _assertError(err, connection, res=undefined){ if(err){ - aliveConnections--; + try{ connection.release(); } @@ -986,7 +943,6 @@ function _assertError(err, connection, res=undefined){ */ function _assertConnectionError(err, res=undefined){ if(err){ - aliveConnections--; if(res != undefined){ res.writeHead(json.badRequest); res.end(); diff --git a/backend/backend.js b/backend/backend.js index 020c060..f642bc7 100644 --- a/backend/backend.js +++ b/backend/backend.js @@ -168,8 +168,7 @@ const server = https.createServer(options, function(req, res){ requestResponse.on("end", function(){ - const response = JSON.parse(data)["data"][0]; - const displayName = response["display_name"]; + let response = JSON.parse(data)["data"]; // If the user is a streamer and their id (which is also // their channel id) can't be found, that means @@ -177,19 +176,32 @@ const server = https.createServer(options, function(req, res){ // changes. So do that. if(requestPayload["role"] == "broadcaster"){ - if(!response["id"] in trackers){ - - singleStreamWebhook(response["id"]); + if(!channelId in trackers){ + singleStreamWebhook(channelId); + } + if(response != undefined){ + response = response[0]; + const displayName = response["display_name"]; + res.setHeader("name", displayName); } - // Then we end and return because we don't want the + // Then we end and return because we don't want the // streamer to accumulate time as well. res.writeHead(json.success, headers); res.end(JSON.stringify(_parseTimes(channelId))); return; } + // ID sharing not on. + if(response == undefined){ + res.writeHead(json.success, headers); + res.end(JSON.stringify(_parseTimes(channelId))); + return; + } + + response = response[0]; + const displayName = response["display_name"]; // If viewer can't be found in the channel's trackers, add // them to it and the SQL tables. @@ -218,9 +230,9 @@ const server = https.createServer(options, function(req, res){ trackers[channelId][displayName].unpauseTime(); } - res.setHeader("name", displayName); } + res.setHeader("name", displayName); res.writeHead(json.success, headers); res.end(JSON.stringify(_parseTimes(channelId))); @@ -282,29 +294,32 @@ const server = https.createServer(options, function(req, res){ // Only allow this request if the streamer is making it. if(requestPayload.role == "broadcaster"){ - - const response = undefined; + + const channelId = requestPayload["channel_id"]; + const viewer = req.headers["viewerqueriedfor"]; + let response = undefined; + let isWhitelisted = + whitelisted[channelId].hasOwnProperty(viewer); // Swap tables for the viewer on the MySQL server. sql.swapViewer(requestPayload["channel_id"], - req.headers["viewerqueriedfor"], - req.headers["whitelisted"]); + viewer, isWhitelisted); // If user wan't whitelisted before, whitelist them now - if(trackers.hasOwnProperty(req.headers["viewerqueriedfor"])){ + if(!isWhitelisted){ - whitelisted[req.headers["viewerqueriedfor"]] = - trackers[req.headers["viewerqueriedfor"]]; - delete trackers[req.headers["viewerqueriedfor"]]; + whitelisted[channelId][viewer] = + trackers[channelId][viewer]; + delete trackers[channelId][viewer]; response = "True"; } // If user was whitelisted, unwhitelist them else{ - trakers[req.headers["viewerqueriedfor"]] = - whitelisted[req.headers["viewerqueriedfor"]]; - delete whitelisted[req.headers["viewerqueriedfor"]]; + trackers[channelId][viewer] = + whitelisted[channelId][viewer]; + delete whitelisted[channelId][viewer]; response = "False"; } @@ -346,13 +361,16 @@ const server = https.createServer(options, function(req, res){ payloadObj; const viewerQueriedFor = req.headers["viewerqueriedfor"]; const channelId = requestPayload["channel_id"]; + let isWhitelisted = undefined; // Checks whether or not user was whitlisted. if(trackers[channelId].hasOwnProperty(viewerQueriedFor)){ res.setHeader("whitelisted", "False"); + isWhitelisted = false; } else{ res.setHeader("whitelisted", "True"); + isWhitelisted = true; } // We check to see if the user was a broadcaster. If they are @@ -367,7 +385,7 @@ const server = https.createServer(options, function(req, res){ res.setHeader("viewerqueriedfor", viewerQueriedFor); - sql.fetchLongTable(channelId, viewerQueriedFor, res); + sql.fetchLongTable(channelId, viewerQueriedFor, isWhitelisted, res); } // Request was not made with a JWT signed by Twitch or something like @@ -390,35 +408,39 @@ const server = https.createServer(options, function(req, res){ const channelId = requestPayload["channel_id"]; - const responsePayload = {}; + const responsePayload = { + viewers: [], + }; // This is super slow. Like, m*n slow where m is the length // of the names and n is the amount of names. Don't know that // time complexity of includes though. There's probably a better // way the search for matching names. for(let viewer in trackers[channelId]){ - if(viewer.includes(requestPayload["viewerqueriedfor"])){ - if(trackers[channelId][viewer] != undefined){ + if(viewer.includes(req.headers["viewerqueriedfor"])){ + let viewVal = trackers[channelId][viewer]; + if(viewVal != undefined){ - responsePayload[viewer] = - trackers[channelId][viewer]["time"]; + responsePayload["viewers"].push([viewer, + viewVal["time"]]); } else{ - responsePayload[viewer] = 0; + responsePayload["viewers"].push([viewer, 0]); } } } for(let viewer in whitelisted[channelId]){ - if(viewer.includes(requestPayload["viewerqueriedfor"])){ - if(whitelisted[channelId][viewer] != undefined){ + if(viewer.includes(req.headers["viewerqueriedfor"])){ + let viewVal = whitelisted[channelId][viewer]; + if(viewVal != undefined){ - responsePayload[viewer] = - whitelisted[channelId][viewer]["time"]; + responsePayload["viewers"].push([viewer, + viewVal["time"]]); } else{ - responsePayload[viewer] = 0; + responsePayload["viewers"].push([viewer, 0]); } } } @@ -468,6 +490,8 @@ const server = https.createServer(options, function(req, res){ } } + res.writeHead(json.success, headers); + res.end(); } else{ res.writeHead(json.forbidden); @@ -834,7 +858,8 @@ function _parseTimes(channelId){ */ function _checkJWT(req, res){ - if(!jwt.verifyJWT(req.headers["extension-jwt"], + if(req.headers["extension-jwt"] == undefined || + !jwt.verifyJWT(req.headers["extension-jwt"], {"b64": json.secret}, {alg: [json.alg]})){ res.writeHead(json.forbidden); diff --git a/frontend/darkmode.css b/frontend/darkmode.css index c42839a..ecd40e2 100644 --- a/frontend/darkmode.css +++ b/frontend/darkmode.css @@ -11,6 +11,33 @@ body { width: 318px; } +/* ID sharing off pop up */ + +#id_off { + animation: expand .5s; + background-color: #342b4d; + left: calc(50% - 129px); + overflow: hidden; + padding: 10px; + position: absolute; + top: 20%; + width: 258px; + z-index: 1000; +} + +@keyframes expand { + from { + transform: scale(0); + } + to { + transform: scale(1); + } +} + +#close_pop { + float: right; +} + /* Title properties */ #title { @@ -39,12 +66,7 @@ body { color: inherit; outline: 0; padding: 2px; - transition: left .5s ease; - width: 239px; -} - -.search_bar_move { - left: calc(50% - 84px); + width: 230px; } /* Style for buttons around the search bar */ @@ -59,6 +81,7 @@ body { } .hide_button { + overflow: hidden; width: 0; } @@ -68,12 +91,6 @@ body { float: right; } -/* Return to regular list from search */ - -#leave_search { - float: left; -} - /* Leaderboard */ .list { @@ -83,6 +100,7 @@ body { color: inherit; height: 20.24px; margin-top: -2.5px; + padding: .5px; width: 318px; } @@ -149,11 +167,17 @@ button:active { width: 318px; } +.list_toggle { + background-color: #4c3c78; + float: right; + height: 30px; +} + /* Individual view */ .individual_view { display: none; - height: 200px; + height: 250px; left: calc(50% - 159px); width: 313px; } diff --git a/frontend/dashboard.html b/frontend/dashboard.html deleted file mode 100644 index 2c9ca42..0000000 --- a/frontend/dashboard.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - Broadcaster Time Leaderboard - - - - - - - - - - - - - - - -
- -
- -
- Watch Time Leaderboard -
- -
- - -
- -
- -
-
- -
- - - - - -
- -
- - - - - - diff --git a/frontend/frontend.js b/frontend/frontend.js index 7255cd8..929c34a 100644 --- a/frontend/frontend.js +++ b/frontend/frontend.js @@ -43,7 +43,7 @@ const SERVER_DOMAIN = "https://vieweranalytics.me:443/"; const INITIAL_BOARD = "initBoard"; const LONG_STATS = "longStats"; const GET_PERIOD = "getPeriod"; -const SEARCH_USER = "searchUser"; +const SEARCH_USER = "userSearch"; const TOGGLE_TRACKER = "toggleTracker"; const TOGGLE_WHITELIST = "toggleWhitelist"; @@ -106,7 +106,21 @@ let viewers = undefined; * as keys and their statistics as values via DOM elements. * !Object */ -const clicked = {}; +let clicked = undefined; + +/** + * Same as viewers array but is used as a reference to save viewers prior to a + * search query. + * !Array> | undefined + */ +let viewersSaved = undefined; + +/** + * Stores people on the leaderboard who have been clicked on the list prior + * to a search query. + * !Object + */ +let clickedSaved = undefined; let currentClicked = undefined; // Name of user who's button was recently // clicked. @@ -117,10 +131,15 @@ let authorization = undefined; // Authorization object. Properties // are specifed on the Twitch Extensions // reference. -let savedBoard = undefined; // DOM element to save the board so +let savedBoard = undefined; // HTML to save the board so // pressing back is seamless. -let paused = false; // Whether or not user is paused. +let recentText = undefined; // Recently submitted text. + +let paused = true; // Whether or not user is paused. + +let displaying = false; // Whether or not search results are being + // displayed. let period = "session"; // Which period of time the user is looking // at @@ -128,6 +147,9 @@ let period = "session"; // Which period of time the user is looking let currentDisplay = 0; // How many people are currently being // displayed on the leaderboard. +let prevDisplay = 0; // How many people were being displayed on + // the leaderboard beofre a search. + $("#" + period).addClass(ACTIVE); //---------- FUNCTIONS / EVENT LISTENERS ----------// @@ -136,43 +158,43 @@ Twitch.ext.actions.requestIdShare(); // Define onAuthorized event. window.Twitch.ext.onAuthorized(function(auth){ - + authorization = auth; _createRequest(INITIAL_BOARD, initBoard); - //TODO Get rid of this line - console.log("onAuthorized fired"); - }); // Define onContext event window.Twitch.ext.onContext(function(cxt, changeArr){ - + // If user is not paused, unpause tracker on the server. - if(changeArr["isPaused"] == false){ - _createRequest(TOGGLE_TRACKER, additionalArgs={ + if(paused && cxt["isPaused"] == false){ + _createRequest(TOGGLE_TRACKER, undefined, additionalArgs={ "paused": false }); + paused = false; } // If user is paused, pause tracker on the server. - else if(changeArr["isPaused"] == true){ - _createRequest(TOGGLE_TRACKER, additionalArgs={ + else if(!paused && cxt["isPaused"] == true){ + _createRequest(TOGGLE_TRACKER, undefined, additionalArgs={ "paused": true }); + paused = true; } // Toggle dark and light css themes. - if(changeArr["theme"] == "light"){ - $("#css").attr("href", LIGHT_MODE); + if(cxt["theme"] == "light"){ + $("#dark").prop("disabled", true); + $("#light").prop("disabled", false); + currTheme = "light" } - else if(changeArr["theme"] == "dark"){ - $("#css").attr("href", DARK_MODE); + else if(cxt["theme"] == "dark"){ + $("#light").prop("disabled", true); + $("#dark").prop("disabled", false); } - console.log("onContext fired"); - }); @@ -182,12 +204,24 @@ $("#refresh").on("click", refresh); // Define functionality when clicking on any of the tabs specifying the // period of time to display. $(".tabtimes").on("click", function(ev){ - + // No need to do anything if the focused tab was clicked again. if(period == this.id){ + + // Unless we recently searched. Then bring back old board. + if(displaying){ + $("#back").trigger("click"); + } + return; } + if(displaying){ + $("#leaderboard").empty(); + _back(); + displaying = false; + } + $(".tabtimes").each(function(){ // Unfocus currently focused button. @@ -210,49 +244,61 @@ $(".tabtimes").on("click", function(ev){ }); +// Define back button event. Bring back board before initial search. +$("#back").on("click", function(ev){ + + // Hide and show the right buttons. Put back old board. + viewers = viewersSaved; + currentDisplay = prevDisplay; + $("#leaderboard").empty(); + $("#leaderboard").append(savedBoard); + clicked = clickedSaved; + _back(); + displaying = false; + +}); + +// Define a means to submit text input. +$("#search").keypress(function(e){ + if(e.which == 13){ + $(this).submit(); + return false; + } +}); + // Define search bar submit event. $("#search").submit(function(ev){ const nameQuery = $(this).val(); - // Don't do anything if input is empty. - if(nameQuery.length == 0){ + // If no change to text box has been made since last submit + // or text is empty, do nothing. + if(nameQuery.length == 0 || nameQuery == recentText){ return; } + recentText = nameQuery; _createRequest(SEARCH_USER, displayResults, additionalArgs={ - "viewerQueriedFor": nameQuery + "viewerqueriedfor": nameQuery }); }); - -// Defind when back button is clicked. -$(window).on("popstate", function(ev){ - - if(savedBoard != undefined){ - $("#leaderboard").replaceWith(savedBoard); - } -}); - - // Before window closes or user leaves page, send request to pause their // tracker. $(window).on("beforeunload", function(){ - _createRequest(TOGGLE_TRACKER, addititionalArgs={ + _createRequest(TOGGLE_TRACKER, undefined, addititionalArgs={ "paused": true }); }); -// Credit to https://gist.github.com/toshimaru/6102647 for this event listener -// that detects the scrolling to the bottom of a page. -$(window).on("scroll", function(){ +// Event listener that detects the scrolling to the bottom of a page. +$("#leaderboard").scroll(function(){ - var scrollHeight = $(document).height(); - var scrollPosition = $(window).height() + $(window).scrollTop(); - if((scrollHeight - scrollPosition) / scrollHeight == 0){ + if($(this)[0].scrollHeight - $(this).scrollTop() - $(this).outerHeight() + < 1){ // Adds another 50 users to the leaderboard. _initButtons(); @@ -274,13 +320,34 @@ function initBoard(res, status=undefined, jqXHR=undefined){ // Disable all buttons until finished initBoard. $(":button").prop("disabled", true); - if(jqXHR != undefined){ + if(jqXHR != undefined && name == undefined){ _setName(jqXHR.getResponseHeader("name")); } + // TODO or if broadcaster if(name == undefined){ - // TODO if name is undefined, notify user to allow id share. + const popUp = $("
", { + id: "id_off" + }); + const text = "

If you want to see yourself on this board, " + + "enable ID sharing! ID sharing just allows the extension" + + "to see your display name. To enable ID sharing, click on" + + " the person icon at the bottom of this extension, then " + + "click \"Grant\".

Note: " + + "The streamer is not shown on the board.

"; + popUp.append(text); + popUp.prepend($("
+