From 4aef15cb11da9eae86865b8a4c51f67ea264e8cf Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 21 Dec 2020 20:19:00 +0000 Subject: [PATCH] lint: collab-client (#4574) * lint: collab-client * Undo incorrect lint fixes These will be re-fixed in a future commit. * Properly fix guard-for-in error * Properly fix prefer-rest-params errors * Move some code back to where it was Moving the code makes it hard to review the diff. * Delete DISCONNECT_REASON case Someone reading the code won't understand what "used to handle appLevelDisconnectReason" means until they dig through the Git history. Given the server never sends messages of type DISCONNECT_REASON anyway, just delete the case. * Refine lint fixes Co-authored-by: Richard Hansen --- src/static/js/collab_client.js | 330 ++++++++++++++++----------------- 1 file changed, 157 insertions(+), 173 deletions(-) diff --git a/src/static/js/collab_client.js b/src/static/js/collab_client.js index a9d748494..abe3911d4 100644 --- a/src/static/js/collab_client.js +++ b/src/static/js/collab_client.js @@ -1,3 +1,5 @@ +'use strict'; + /** * This code is mostly from the old Etherpad. Please help us to comment this code. * This helps other people to understand this code better and helps them to improve it. @@ -22,13 +24,12 @@ const chat = require('./chat').chat; const hooks = require('./pluginfw/hooks'); +const browser = require('./browser'); // Dependency fill on init. This exists for `pad.socket` only. // TODO: bind directly to the socket. let pad = undefined; -function getSocket() { - return pad && pad.socket; -} +const getSocket = () => pad && pad.socket; /** Call this when the document is ready, and a new Ace2Editor() has been created and inited. ACE's ready callback does not need to have fired yet. @@ -38,13 +39,9 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) pad = _pad; // Inject pad to avoid a circular dependency. let rev = serverVars.rev; - const padId = serverVars.padId; - let state = 'IDLE'; let stateMessage; let channelState = 'CONNECTING'; - let appLevelDisconnectReason = null; - let lastCommitTime = 0; let initialStartConnectTime = 0; @@ -56,46 +53,34 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) const caughtErrors = []; const caughtErrorCatchers = []; const caughtErrorTimes = []; - const debugMessages = []; const msgQueue = []; let isPendingRevision = false; - tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData); - tellAceActiveAuthorInfo(initialUserInfo); - const callbacks = { - onUserJoin() {}, - onUserLeave() {}, - onUpdateUserInfo() {}, - onChannelStateChange() {}, - onClientMessage() {}, - onInternalAction() {}, - onConnectionTrouble() {}, + onUserJoin: () => {}, + onUserLeave: () => {}, + onUpdateUserInfo: () => {}, + onChannelStateChange: () => {}, + onClientMessage: () => {}, + onInternalAction: () => {}, + onConnectionTrouble: () => {}, + onServerMessage: () => {}, }; if (browser.firefox) { // Prevent "escape" from taking effect and canceling a comet connection; // doesn't work if focus is on an iframe. $(window).bind('keydown', (evt) => { - if (evt.which == 27) { + if (evt.which === 27) { evt.preventDefault(); } }); } - editor.setProperty('userAuthor', userId); - editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool); - editor.setUserChangeNotificationCallback(wrapRecordingErrors('handleUserChanges', handleUserChanges)); - - function dmesg(str) { - if (typeof window.ajlog === 'string') window.ajlog += `${str}\n`; - debugMessages.push(str); - } - - function handleUserChanges() { + const handleUserChanges = () => { if (editor.getInInternationalComposition()) return; - if ((!getSocket()) || channelState == 'CONNECTING') { - if (channelState == 'CONNECTING' && (((+new Date()) - initialStartConnectTime) > 20000)) { + if ((!getSocket()) || channelState === 'CONNECTING') { + if (channelState === 'CONNECTING' && (((+new Date()) - initialStartConnectTime) > 20000)) { setChannelState('DISCONNECTED', 'initsocketfail'); } else { // check again in a bit @@ -106,11 +91,11 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) const t = (+new Date()); - if (state != 'IDLE') { - if (state == 'COMMITTING' && msgQueue.length == 0 && (t - lastCommitTime) > 20000) { + if (state !== 'IDLE') { + if (state === 'COMMITTING' && msgQueue.length === 0 && (t - lastCommitTime) > 20000) { // a commit is taking too long setChannelState('DISCONNECTED', 'slowcommit'); - } else if (state == 'COMMITTING' && msgQueue.length == 0 && (t - lastCommitTime) > 5000) { + } else if (state === 'COMMITTING' && msgQueue.length === 0 && (t - lastCommitTime) > 5000) { callbacks.onConnectionTrouble('SLOW'); } else { // run again in a few seconds, to detect a disconnect @@ -121,17 +106,19 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) const earliestCommit = lastCommitTime + 500; if (t < earliestCommit) { - setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), earliestCommit - t); + setTimeout( + wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), + earliestCommit - t); return; } // apply msgQueue changeset. - if (msgQueue.length != 0) { + if (msgQueue.length !== 0) { let msg; - while (msg = msgQueue.shift()) { + while ((msg = msgQueue.shift())) { const newRev = msg.newRev; rev = newRev; - if (msg.type == 'ACCEPT_COMMIT') { + if (msg.type === 'ACCEPT_COMMIT') { editor.applyPreparedChangesetToBase(); setStateIdle(); callCatchingErrors('onInternalAction', () => { @@ -141,7 +128,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) callbacks.onConnectionTrouble('OK'); }); handleUserChanges(); - } else if (msg.type == 'NEW_CHANGES') { + } else if (msg.type === 'NEW_CHANGES') { const changeset = msg.changeset; const author = (msg.author || ''); const apool = msg.apool; @@ -181,67 +168,60 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) // run again in a few seconds, to detect a disconnect setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000); } - } + }; - function setUpSocket() { - hiccupCount = 0; + const setUpSocket = () => { setChannelState('CONNECTED'); doDeferredActions(); initialStartConnectTime = +new Date(); - } + }; - var hiccupCount = 0; - - function sendMessage(msg) { + const sendMessage = (msg) => { getSocket().json.send( { type: 'COLLABROOM', component: 'pad', data: msg, }); - } + }; - function wrapRecordingErrors(catcher, func) { - return function () { - try { - return func.apply(this, Array.prototype.slice.call(arguments)); - } catch (e) { - caughtErrors.push(e); - caughtErrorCatchers.push(catcher); - caughtErrorTimes.push(+new Date()); - // console.dir({catcher: catcher, e: e}); - throw e; - } - }; - } + const wrapRecordingErrors = (catcher, func) => function (...args) { + try { + return func.call(this, ...args); + } catch (e) { + caughtErrors.push(e); + caughtErrorCatchers.push(catcher); + caughtErrorTimes.push(+new Date()); + // console.dir({catcher: catcher, e: e}); + throw e; + } + }; - function callCatchingErrors(catcher, func) { + const callCatchingErrors = (catcher, func) => { try { wrapRecordingErrors(catcher, func)(); } catch (e) { /* absorb*/ } - } + }; - function handleMessageFromServer(evt) { + const handleMessageFromServer = (evt) => { if (!getSocket()) return; if (!evt.data) return; const wrapper = evt; - if (wrapper.type != 'COLLABROOM' && wrapper.type != 'CUSTOM') return; + if (wrapper.type !== 'COLLABROOM' && wrapper.type !== 'CUSTOM') return; const msg = wrapper.data; - if (msg.type == 'NEW_CHANGES') { - var newRev = msg.newRev; - var changeset = msg.changeset; - var author = (msg.author || ''); - var apool = msg.apool; + if (msg.type === 'NEW_CHANGES') { + const newRev = msg.newRev; + const changeset = msg.changeset; + const author = (msg.author || ''); + const apool = msg.apool; // When inInternationalComposition, msg pushed msgQueue. if (msgQueue.length > 0 || editor.getInInternationalComposition()) { - if (msgQueue.length > 0) var oldRev = msgQueue[msgQueue.length - 1].newRev; - else oldRev = rev; - - if (newRev != (oldRev + 1)) { + const oldRev = msgQueue.length > 0 ? msgQueue[msgQueue.length - 1].newRev : rev; + if (newRev !== (oldRev + 1)) { window.console.warn(`bad message revision on NEW_CHANGES: ${newRev} not ${oldRev + 1}`); // setChannelState("DISCONNECTED", "badmessage_newchanges"); return; @@ -250,7 +230,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) return; } - if (newRev != (rev + 1)) { + if (newRev !== (rev + 1)) { window.console.warn(`bad message revision on NEW_CHANGES: ${newRev} not ${rev + 1}`); // setChannelState("DISCONNECTED", "badmessage_newchanges"); return; @@ -258,11 +238,12 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) rev = newRev; editor.applyChangesToBase(changeset, author, apool); - } else if (msg.type == 'ACCEPT_COMMIT') { - var newRev = msg.newRev; + } else if (msg.type === 'ACCEPT_COMMIT') { + const newRev = msg.newRev; if (msgQueue.length > 0) { - if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) { - window.console.warn(`bad message revision on ACCEPT_COMMIT: ${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`); + if (newRev !== (msgQueue[msgQueue.length - 1].newRev + 1)) { + window.console.warn('bad message revision on ACCEPT_COMMIT: ' + + `${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`); // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); return; } @@ -270,7 +251,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) return; } - if (newRev != (rev + 1)) { + if (newRev !== (rev + 1)) { window.console.warn(`bad message revision on ACCEPT_COMMIT: ${newRev} not ${rev + 1}`); // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); return; @@ -285,9 +266,9 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) callbacks.onConnectionTrouble('OK'); }); handleUserChanges(); - } else if (msg.type == 'CLIENT_RECONNECT') { - // Server sends a CLIENT_RECONNECT message when there is a client reconnect. Server also returns - // all pending revisions along with this CLIENT_RECONNECT message + } else if (msg.type === 'CLIENT_RECONNECT') { + // Server sends a CLIENT_RECONNECT message when there is a client reconnect. + // Server also returns all pending revisions along with this CLIENT_RECONNECT message if (msg.noChanges) { // If no revisions are pending, just make everything normal setIsPendingRevision(false); @@ -295,14 +276,15 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) } const headRev = msg.headRev; - var newRev = msg.newRev; - var changeset = msg.changeset; - var author = (msg.author || ''); - var apool = msg.apool; + const newRev = msg.newRev; + const changeset = msg.changeset; + const author = (msg.author || ''); + const apool = msg.apool; if (msgQueue.length > 0) { - if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) { - window.console.warn(`bad message revision on CLIENT_RECONNECT: ${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`); + if (newRev !== (msgQueue[msgQueue.length - 1].newRev + 1)) { + window.console.warn('bad message revision on CLIENT_RECONNECT: ' + + `${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`); // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); return; } @@ -311,14 +293,14 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) return; } - if (newRev != (rev + 1)) { + if (newRev !== (rev + 1)) { window.console.warn(`bad message revision on CLIENT_RECONNECT: ${newRev} not ${rev + 1}`); // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); return; } rev = newRev; - if (author == pad.getUserId()) { + if (author === pad.getUserId()) { editor.applyPreparedChangesetToBase(); setStateIdle(); callCatchingErrors('onInternalAction', () => { @@ -332,19 +314,19 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) editor.applyChangesToBase(changeset, author, apool); } - if (newRev == headRev) { + if (newRev === headRev) { // Once we have applied all pending revisions, make everything normal setIsPendingRevision(false); } - } else if (msg.type == 'NO_COMMIT_PENDING') { - if (state == 'COMMITTING') { + } else if (msg.type === 'NO_COMMIT_PENDING') { + if (state === 'COMMITTING') { // server missed our commit message; abort that commit setStateIdle(); handleUserChanges(); } - } else if (msg.type == 'USER_NEWINFO') { - var userInfo = msg.userInfo; - var id = userInfo.userId; + } else if (msg.type === 'USER_NEWINFO') { + const userInfo = msg.userInfo; + const id = userInfo.userId; // Avoid a race condition when setting colors. If our color was set by a // query param, ignore our own "new user" message's color value. @@ -361,21 +343,19 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) callbacks.onUserJoin(userInfo); } tellAceActiveAuthorInfo(userInfo); - } else if (msg.type == 'USER_LEAVE') { - var userInfo = msg.userInfo; - var id = userInfo.userId; + } else if (msg.type === 'USER_LEAVE') { + const userInfo = msg.userInfo; + const id = userInfo.userId; if (userSet[id]) { delete userSet[userInfo.userId]; fadeAceAuthorInfo(userInfo); callbacks.onUserLeave(userInfo); } - } else if (msg.type == 'DISCONNECT_REASON') { - appLevelDisconnectReason = msg.reason; - } else if (msg.type == 'CLIENT_MESSAGE') { + } else if (msg.type === 'CLIENT_MESSAGE') { callbacks.onClientMessage(msg.payload); - } else if (msg.type == 'CHAT_MESSAGE') { + } else if (msg.type === 'CHAT_MESSAGE') { chat.addMessage(msg, true, false); - } else if (msg.type == 'CHAT_MESSAGES') { + } else if (msg.type === 'CHAT_MESSAGES') { for (let i = msg.messages.length - 1; i >= 0; i--) { chat.addMessage(msg.messages[i], true, true); } @@ -389,12 +369,18 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) $('#chatloadmessagesball').css('display', 'none'); // there are less than 100 messages or we reached the top - if (chat.historyPointer <= 0) { $('#chatloadmessagesbutton').css('display', 'none'); } else // there are still more messages, re-show the load-button - { $('#chatloadmessagesbutton').css('display', 'block'); } + if (chat.historyPointer <= 0) { + $('#chatloadmessagesbutton').css('display', 'none'); + } else { + // there are still more messages, re-show the load-button + $('#chatloadmessagesbutton').css('display', 'block'); + } } - // HACKISH: User messages do not have "payload" but "userInfo", so that all "handleClientMessage_USER_" hooks would work, populate payload - // FIXME: USER_* messages to have "payload" property instead of "userInfo", seems like a quite a big work + // HACKISH: User messages do not have "payload" but "userInfo", so that all + // "handleClientMessage_USER_" hooks would work, populate payload + // FIXME: USER_* messages to have "payload" property instead of "userInfo", + // seems like a quite a big work if (msg.type.indexOf('USER_') > -1) { msg.payload = msg.userInfo; } @@ -402,9 +388,9 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) if (msg.type === 'NEW_CHANGES') msg.payload = msg; hooks.callAll(`handleClientMessage_${msg.type}`, {payload: msg.payload}); - } + }; - function updateUserInfo(userInfo) { + const updateUserInfo = (userInfo) => { userInfo.userId = userId; userSet[userId] = userInfo; tellAceActiveAuthorInfo(userInfo); @@ -414,13 +400,13 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) type: 'USERINFO_UPDATE', userInfo, }); - } + }; - function tellAceActiveAuthorInfo(userInfo) { + const tellAceActiveAuthorInfo = (userInfo) => { tellAceAuthorInfo(userInfo.userId, userInfo.colorId); - } + }; - function tellAceAuthorInfo(userId, colorId, inactive) { + const tellAceAuthorInfo = (userId, colorId, inactive) => { if (typeof colorId === 'number') { colorId = clientVars.colorPalette[colorId]; } @@ -436,91 +422,81 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) bgcolor: cssColor, }); } - } + }; - function fadeAceAuthorInfo(userInfo) { + const fadeAceAuthorInfo = (userInfo) => { tellAceAuthorInfo(userInfo.userId, userInfo.colorId, true); - } + }; - function getConnectedUsers() { - return valuesArray(userSet); - } + const getConnectedUsers = () => valuesArray(userSet); - function tellAceAboutHistoricalAuthors(hadata) { - for (const author in hadata) { - const data = hadata[author]; + const tellAceAboutHistoricalAuthors = (hadata) => { + for (const [author, data] of Object.entries(hadata)) { if (!userSet[author]) { tellAceAuthorInfo(author, data.colorId, true); } } - } + }; - function setChannelState(newChannelState, moreInfo) { - if (newChannelState != channelState) { + const setChannelState = (newChannelState, moreInfo) => { + if (newChannelState !== channelState) { channelState = newChannelState; callbacks.onChannelStateChange(channelState, moreInfo); } - } + }; - function valuesArray(obj) { + const valuesArray = (obj) => { const array = []; $.each(obj, (k, v) => { array.push(v); }); return array; - } + }; // We need to present a working interface even before the socket // is connected for the first time. let deferredActions = []; - function defer(func, tag) { - return function () { - const that = this; - const args = arguments; - - function action() { - func.apply(that, args); - } - action.tag = tag; - if (channelState == 'CONNECTING') { - deferredActions.push(action); - } else { - action(); - } + const defer = (func, tag) => function (...args) { + const action = () => { + func.call(this, ...args); }; - } + action.tag = tag; + if (channelState === 'CONNECTING') { + deferredActions.push(action); + } else { + action(); + } + }; - function doDeferredActions(tag) { + const doDeferredActions = (tag) => { const newArray = []; for (let i = 0; i < deferredActions.length; i++) { const a = deferredActions[i]; - if ((!tag) || (tag == a.tag)) { + if ((!tag) || (tag === a.tag)) { a(); } else { newArray.push(a); } } deferredActions = newArray; - } + }; - function sendClientMessage(msg) { + const sendClientMessage = (msg) => { sendMessage( { type: 'CLIENT_MESSAGE', payload: msg, }); - } + }; - function getCurrentRevisionNumber() { - return rev; - } + const getCurrentRevisionNumber = () => rev; - function getMissedChanges() { + const getMissedChanges = () => { const obj = {}; obj.userInfo = userSet[userId]; obj.baseRev = rev; - if (state == 'COMMITTING' && stateMessage) { + if (state === 'COMMITTING' && stateMessage) { obj.committedChangeset = stateMessage.changeset; obj.committedChangesetAPool = stateMessage.apool; editor.applyPreparedChangesetToBase(); @@ -531,56 +507,56 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) obj.furtherChangesetAPool = userChangesData.apool; } return obj; - } + }; - function setStateIdle() { + const setStateIdle = () => { state = 'IDLE'; callbacks.onInternalAction('newlyIdle'); schedulePerhapsCallIdleFuncs(); - } + }; - function setIsPendingRevision(value) { + const setIsPendingRevision = (value) => { isPendingRevision = value; - } + }; - function callWhenNotCommitting(func) { + const idleFuncs = []; + + const callWhenNotCommitting = (func) => { idleFuncs.push(func); schedulePerhapsCallIdleFuncs(); - } + }; - var idleFuncs = []; - - function schedulePerhapsCallIdleFuncs() { + const schedulePerhapsCallIdleFuncs = () => { setTimeout(() => { - if (state == 'IDLE') { + if (state === 'IDLE') { while (idleFuncs.length > 0) { const f = idleFuncs.shift(); f(); } } }, 0); - } + }; const self = { - setOnUserJoin(cb) { + setOnUserJoin: (cb) => { callbacks.onUserJoin = cb; }, - setOnUserLeave(cb) { + setOnUserLeave: (cb) => { callbacks.onUserLeave = cb; }, - setOnUpdateUserInfo(cb) { + setOnUpdateUserInfo: (cb) => { callbacks.onUpdateUserInfo = cb; }, - setOnChannelStateChange(cb) { + setOnChannelStateChange: (cb) => { callbacks.onChannelStateChange = cb; }, - setOnClientMessage(cb) { + setOnClientMessage: (cb) => { callbacks.onClientMessage = cb; }, - setOnInternalAction(cb) { + setOnInternalAction: (cb) => { callbacks.onInternalAction = cb; }, - setOnConnectionTrouble(cb) { + setOnConnectionTrouble: (cb) => { callbacks.onConnectionTrouble = cb; }, updateUserInfo: defer(updateUserInfo), @@ -597,6 +573,14 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) setIsPendingRevision, }; + tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData); + tellAceActiveAuthorInfo(initialUserInfo); + + editor.setProperty('userAuthor', userId); + editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool); + editor.setUserChangeNotificationCallback( + wrapRecordingErrors('handleUserChanges', handleUserChanges)); + setUpSocket(); return self; }