From a0177e5d3ca132352d2a33a6a69e47c302c7be93 Mon Sep 17 00:00:00 2001 From: Chad Weider Date: Sun, 16 Sep 2012 18:07:55 -0700 Subject: [PATCH 01/12] Remember, the `class` symbol is reserved in some environments. Fixes issue introduced in 9be69ef2582dfc2c1b050041d4586cbd90d20e2c. --- src/static/js/linestylefilter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/linestylefilter.js b/src/static/js/linestylefilter.js index 4231d26b6..c6434b6c2 100644 --- a/src/static/js/linestylefilter.js +++ b/src/static/js/linestylefilter.js @@ -150,7 +150,7 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun var disableAuthColorForThisLine = hooks.callAll("disableAuthorColorsForThisLine", { linestylefilter: linestylefilter, text: txt, - class: cls + "class": cls }, " ", " ", ""); var disableAuthors = (disableAuthColorForThisLine==null||disableAuthColorForThisLine.length==0)?false:disableAuthColorForThisLine[0]; while (txt.length > 0) From bbc8848af3460a8dca63f53bd575471faa7ec12e Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 17 Sep 2012 16:29:39 +0200 Subject: [PATCH 02/12] Still support API endpoints of v1 in v1.1 --- src/node/handler/APIHandler.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index be14daa86..2af7afef3 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -70,7 +70,35 @@ var version = , "padUsersCount" : ["padID"] } , "1.1": - { "getAuthorName" : ["authorID"] + { "createGroup" : [] + , "createGroupIfNotExistsFor" : ["groupMapper"] + , "deleteGroup" : ["groupID"] + , "listPads" : ["groupID"] + , "createPad" : ["padID", "text"] + , "createGroupPad" : ["groupID", "padName", "text"] + , "createAuthor" : ["name"] + , "createAuthorIfNotExistsFor": ["authorMapper" , "name"] + , "listPadsOfAuthor" : ["authorID"] + , "createSession" : ["groupID", "authorID", "validUntil"] + , "deleteSession" : ["sessionID"] + , "getSessionInfo" : ["sessionID"] + , "listSessionsOfGroup" : ["groupID"] + , "listSessionsOfAuthor" : ["authorID"] + , "getText" : ["padID", "rev"] + , "setText" : ["padID", "text"] + , "getHTML" : ["padID", "rev"] + , "setHTML" : ["padID", "html"] + , "getRevisionsCount" : ["padID"] + , "getLastEdited" : ["padID"] + , "deletePad" : ["padID"] + , "getReadOnlyID" : ["padID"] + , "setPublicStatus" : ["padID", "publicStatus"] + , "getPublicStatus" : ["padID"] + , "setPassword" : ["padID", "password"] + , "isPasswordProtected" : ["padID"] + , "listAuthorsOfPad" : ["padID"] + , "padUsersCount" : ["padID"] + , "getAuthorName" : ["authorID"] , "padUsers" : ["padID"] , "sendClientsMessage" : ["padID", "msg"] } From f8f002adc0ec152b15d67bca9b5288d6b46196e1 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 17 Sep 2012 23:03:56 +0200 Subject: [PATCH 03/12] Add listAllGroups API endpoint Adds a database key that lists all groups --- doc/api/http_api.md | 4 ++ doc/database.md | 3 ++ src/node/db/API.js | 1 + src/node/db/GroupManager.js | 70 +++++++++++++++++++++++++++++++++- src/node/handler/APIHandler.js | 31 ++++++++++++++- 5 files changed, 107 insertions(+), 2 deletions(-) diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 058a2ba6c..3afab498f 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -135,6 +135,10 @@ Pads can belong to a group. The padID of grouppads is starting with a groupID li * `{code: 1, message:"pad does already exist", data: null}` * `{code: 1, message:"groupID does not exist", data: null}` +* **listAllGroups()** lists all existing groups

*Example returns:* + * `{code: 0, message:"ok", data: {groupIDs: ["g.mKjkmnAbSMtCt8eL", "g.3ADWx6sbGuAiUmCy"]}}` + * `{code: 0, message:"ok", data: {groupIDs: []}}` + ### Author These authors are bound to the attributes the users choose (color and name). diff --git a/doc/database.md b/doc/database.md index 2e06e2064..de3e9f547 100644 --- a/doc/database.md +++ b/doc/database.md @@ -2,6 +2,9 @@ ## Keys and their values +### groups +A list of all existing groups (a JSON object with groupIDs as keys and `1` as values). + ### pad:$PADID Saves all informations about pads diff --git a/src/node/db/API.js b/src/node/db/API.js index c5caae0be..4979e8c65 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -35,6 +35,7 @@ var cleanText = require("./Pad").cleanText; /**GROUP FUNCTIONS*****/ /**********************/ +exports.listAllGroups = groupManager.listAllGroups; exports.createGroup = groupManager.createGroup; exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor; exports.deleteGroup = groupManager.deleteGroup; diff --git a/src/node/db/GroupManager.js b/src/node/db/GroupManager.js index bd19507ff..81b0cb9ef 100644 --- a/src/node/db/GroupManager.js +++ b/src/node/db/GroupManager.js @@ -26,6 +26,24 @@ var db = require("./DB").db; var async = require("async"); var padManager = require("./PadManager"); var sessionManager = require("./SessionManager"); + +exports.listAllGroups = function(callback) { + db.get("groups", function (err, groups) { + if(ERR(err, callback)) return; + + // there are no groups + if(groups == null) { + callback(null, {groupIDs: []}); + return; + } + + var groupIDs = []; + for ( var groupID in groups ) { + groupIDs.push(groupID); + } + callback(null, {groupIDs: groupIDs}); + }); +} exports.deleteGroup = function(groupID, callback) { @@ -105,6 +123,39 @@ exports.deleteGroup = function(groupID, callback) db.remove("group2sessions:" + groupID); db.remove("group:" + groupID); callback(); + }, + //unlist the group + function(callback) + { + exports.listAllGroups(function(err, groups) { + if(ERR(err, callback)) return; + groups = groups? groups.groupIDs : []; + + // it's not listed + if(groups.indexOf(groupID) == -1) { + callback(); + return; + } + + groups.splice(groups.indexOf(groupID), 1); + + // store empty groupe list + if(groups.length == 0) { + db.set("groups", {}); + callback(); + return; + } + + // regenerate group list + var newGroups = {}; + async.forEach(groups, function(group, cb) { + newGroups[group] = 1; + cb(); + },function() { + db.set("groups", newGroups); + callback(); + }); + }); } ], function(err) { @@ -130,7 +181,24 @@ exports.createGroup = function(callback) //create the group db.set("group:" + groupID, {pads: {}}); - callback(null, {groupID: groupID}); + + //list the group + exports.listAllGroups(function(err, groups) { + if(ERR(err, callback)) return; + groups = groups? groups.groupIDs : []; + + groups.push(groupID); + + // regenerate group list + var newGroups = {}; + async.forEach(groups, function(group, cb) { + newGroups[group] = 1; + cb(); + },function() { + db.set("groups", newGroups); + callback(null, {groupID: groupID}); + }); + }); } exports.createGroupIfNotExistsFor = function(groupMapper, callback) diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index be14daa86..f99762cea 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -70,9 +70,38 @@ var version = , "padUsersCount" : ["padID"] } , "1.1": - { "getAuthorName" : ["authorID"] + { "createGroup" : [] + , "createGroupIfNotExistsFor" : ["groupMapper"] + , "deleteGroup" : ["groupID"] + , "listPads" : ["groupID"] + , "createPad" : ["padID", "text"] + , "createGroupPad" : ["groupID", "padName", "text"] + , "createAuthor" : ["name"] + , "createAuthorIfNotExistsFor": ["authorMapper" , "name"] + , "listPadsOfAuthor" : ["authorID"] + , "createSession" : ["groupID", "authorID", "validUntil"] + , "deleteSession" : ["sessionID"] + , "getSessionInfo" : ["sessionID"] + , "listSessionsOfGroup" : ["groupID"] + , "listSessionsOfAuthor" : ["authorID"] + , "getText" : ["padID", "rev"] + , "setText" : ["padID", "text"] + , "getHTML" : ["padID", "rev"] + , "setHTML" : ["padID", "html"] + , "getRevisionsCount" : ["padID"] + , "getLastEdited" : ["padID"] + , "deletePad" : ["padID"] + , "getReadOnlyID" : ["padID"] + , "setPublicStatus" : ["padID", "publicStatus"] + , "getPublicStatus" : ["padID"] + , "setPassword" : ["padID", "password"] + , "isPasswordProtected" : ["padID"] + , "listAuthorsOfPad" : ["padID"] + , "padUsersCount" : ["padID"] + , "getAuthorName" : ["authorID"] , "padUsers" : ["padID"] , "sendClientsMessage" : ["padID", "msg"] + , "listAllGroups" : [] } }; From 923b51033b1a0a2e358371650393c7460e65ad14 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 18 Sep 2012 15:54:08 +0200 Subject: [PATCH 04/12] List 12 plugins instead of 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4 was a bit stingy :)  12 is a bit more friendly from a UX persepctive. --- src/static/js/admin/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index 742c3bb22..93acf6ea5 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -15,7 +15,7 @@ $(document).ready(function () { $('.search-results').data('query', { pattern: '', offset: 0, - limit: 4, + limit: 12, }); var doUpdate = false; From 443a71bc9ce33b150fbfd06332a20b072250c24e Mon Sep 17 00:00:00 2001 From: johnyma22 Date: Tue, 18 Sep 2012 16:30:26 +0100 Subject: [PATCH 05/12] Fixed foreach loop on session IDs, was breaking EP on single session in cookie. --- src/node/db/SecurityManager.js | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 1894ee59a..7e3581746 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -123,26 +123,29 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) } var sessionIDs = sessionCookie.split(','); - async.foreach(sessionIDs, function(sessionID) { - sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { - //skip session if it doesn't exist - if(err && err.message == "sessionID does not exist") return; + if (sessionIDs){ + async.forEach(sessionIDs, function(sessionID, cb){ + sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { + //skip session if it doesn't exist + if(err && err.message == "sessionID does not exist") return; - if(ERR(err, callback)) return; + if(ERR(err, callback)) return; - var now = Math.floor(new Date().getTime()/1000); + var now = Math.floor(new Date().getTime()/1000); - //is it for this group? - if(sessionInfo.groupID != groupID) return; + //is it for this group? + if(sessionInfo.groupID != groupID) return; - //is validUntil still ok? - if(sessionInfo.validUntil <= now) return; + //is validUntil still ok? + if(sessionInfo.validUntil <= now) return; - // There is a valid session - validSession = true; - sessionAuthor = sessionInfo.authorID; - }); - }, callback) + // There is a valid session + validSession = true; + sessionAuthor = sessionInfo.authorID; + cb(); // finish the current value and go to next + }); + }, callback) + } }, //get author for token function(callback) From b9da0e187edc05626f315d781ec2b0d626b88dba Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 19 Sep 2012 17:42:36 +0200 Subject: [PATCH 06/12] Revert "Fixed foreach loop on session IDs, was breaking EP on single session in cookie." This reverts commit 443a71bc9ce33b150fbfd06332a20b072250c24e. modified: src/node/db/SecurityManager.js --- src/node/db/SecurityManager.js | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 7e3581746..1894ee59a 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -123,29 +123,26 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) } var sessionIDs = sessionCookie.split(','); - if (sessionIDs){ - async.forEach(sessionIDs, function(sessionID, cb){ - sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { - //skip session if it doesn't exist - if(err && err.message == "sessionID does not exist") return; + async.foreach(sessionIDs, function(sessionID) { + sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { + //skip session if it doesn't exist + if(err && err.message == "sessionID does not exist") return; - if(ERR(err, callback)) return; + if(ERR(err, callback)) return; - var now = Math.floor(new Date().getTime()/1000); + var now = Math.floor(new Date().getTime()/1000); - //is it for this group? - if(sessionInfo.groupID != groupID) return; + //is it for this group? + if(sessionInfo.groupID != groupID) return; - //is validUntil still ok? - if(sessionInfo.validUntil <= now) return; + //is validUntil still ok? + if(sessionInfo.validUntil <= now) return; - // There is a valid session - validSession = true; - sessionAuthor = sessionInfo.authorID; - cb(); // finish the current value and go to next - }); - }, callback) - } + // There is a valid session + validSession = true; + sessionAuthor = sessionInfo.authorID; + }); + }, callback) }, //get author for token function(callback) From a72ade4494ad0df10fd99bd7692d73546e5284f7 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 19 Sep 2012 17:48:26 +0200 Subject: [PATCH 07/12] Fix async.forEach in MultiSession code --- src/node/db/SecurityManager.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 1894ee59a..59e27b550 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -123,7 +123,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) } var sessionIDs = sessionCookie.split(','); - async.foreach(sessionIDs, function(sessionID) { + async.forEach(sessionIDs, function(sessionID, callback) { sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { //skip session if it doesn't exist if(err && err.message == "sessionID does not exist") return; @@ -141,8 +141,10 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) // There is a valid session validSession = true; sessionAuthor = sessionInfo.authorID; + + callback(); }); - }, callback) + }, callback); }, //get author for token function(callback) From 49915dfeb8627e28104aee666eec019646eea5ef Mon Sep 17 00:00:00 2001 From: Chad Weider Date: Fri, 21 Sep 2012 21:58:49 -0700 Subject: [PATCH 08/12] Upgrade to Yajsml with another Windows backslash fix. --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index ca0bdbdca..2285f0ca8 100644 --- a/src/package.json +++ b/src/package.json @@ -10,7 +10,7 @@ "name": "Robin Buse" } ], "dependencies" : { - "yajsml" : "1.1.4", + "yajsml" : "1.1.5", "request" : "2.9.100", "require-kernel" : "1.0.5", "resolve" : "0.2.x", From 087560ea6c9b75e2ffdb36d452463d6891f39e07 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Sep 2012 12:55:49 +0200 Subject: [PATCH 09/12] Let Github know our Dev Guidelines https://github.com/blog/1184-contributing-guidelines --- CONTRIBUTING.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5f081f27e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Developer Guidelines + +Please talk to people on the mailing list before you change this page + +Mailing list: https://groups.google.com/forum/?fromgroups#!forum/etherpad-lite-dev + +IRC channels: [#etherpad](irc://freenode/#etherpad) ([webchat](webchat.freenode.net?channels=etherpad)), [#etherpad-lite-dev](irc://freenode/#etherpad-lite-dev) ([webchat](webchat.freenode.net?channels=etherpad-lite-dev)) + +**Our goal is to iterate in small steps. Release often, release early. Evolution instead of a revolution** + +## General goals of Etherpad Lite +* easy to install for admins +* easy to use for people +* using less resources on server side +* easy to embed for admins +* also runable as etherpad lite only +* keep it maintainable, we don't wanna end ob as the monster Etherpad was +* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core + +## How to code: +* **Please write comments**. I don't mean you have to comment every line and every loop. I just mean, if you do anything thats a bit complex or a bit weird, please leave a comment. It's easy to do that if you do while you're writing the code. Keep in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are worthless +* Never ever use tabs +* Indentation: JS/CSS: 2 spaces; HTML: 4 spaces +* Don't overengineer. Don't try to solve any possible problem in one step. Try to solve problems as easy as possible and improve the solution over time +* Do generalize sooner or later - if an old solution hacked together according to the above point, poses more problems than it solves today, reengineer it, with the lessons learned taken into account. +* Keep it compatible to API-Clients/older DBs/configurations. Don't make incompatible changes the protocol/database format without good reasons + +## How to work with git +* Make a new branch for every feature you're working on. Don't work in your master branch. This ensures that you can work you can do lot of small pull requests instead of one big one with complete different features +* Don't use the online edit function of github. This only creates ugly and not working commits +* Test before you push. Sounds easy, it isn't +* Try to make clean commits that are easy readable +* Don't check in stuff that gets generated during build or runtime (like jquery, minified files, dbs etc...) +* Make pull requests from your feature branch to our develop branch once your feature is ready +* Make small pull requests that are easy to review but make sure they do add value by themselves / individually + +## Branching model in Etherpad Lite +see git flow http://nvie.com/posts/a-successful-git-branching-model/ + +* master, the stable. This is the branch everyone should use for production stuff +* develop, everything that is READY to go into master at some point in time. This stuff is tested and ready to go out +* release branches, stuff that should go into master very soon, only bugfixes go into these (see http://nvie.com/posts/a-successful-git-branching-model/ for why) +* you can set tags in the master branch, there is no real need for release branches imho +* The latest tag is not what is shown in github by default. Doing a clone of master should give you latest stable, not what is gonna be latest stable in a week, also, we should not be blocking new features to develop, just because we feel that we should be releasing it to master soon. This is the situation that release branches solve/handle. +* hotfix branches, fixes for bugs in master +* feature branches (in your own repos), these are the branches where you develop your features in. If its ready to go out, it will be merged into develop + +Over the time we pull features from feature branches into the develop branch. Every month we pull from develop into master. Bugs in master get fixed in hotfix branches. These branches will get merged into master AND develop. There should never be commits in master that aren't in develop + +## Documentation +The docs are in the `doc/` folder in the git repository, so people can easily find the suitable docs for the current git revision. + +Documentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request. + +You can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet. \ No newline at end of file From e16008b3719855d95cf0bb3402a0ac67eb319d47 Mon Sep 17 00:00:00 2001 From: Richard Braakman Date: Wed, 26 Sep 2012 03:01:53 +0300 Subject: [PATCH 10/12] Fix sessioninfos race that can cause crash during USER_CHANGES handling When stress testing etherpad-lite we occasionally got this error: TypeError: Cannot read property 'author' of undefined at /home/etherpad/etherpad-lite/src/node/handler/PadMessageHandler.js:556:47 handleUserChanges was accessing sessioninfos[client.id].author in a callback, after spending some time in the loop that updates the changeset to the latest revision. It's possible for a disconnect request to be processed during that loop so the session might no longer be there. This patch fixes it by looking up the author at the start of the function. --- src/node/handler/PadMessageHandler.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 913433b01..160686804 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -465,6 +465,7 @@ function handleUserChanges(client, message) var baseRev = message.data.baseRev; var wireApool = (new AttributePool()).fromJsonable(message.data.apool); var changeset = message.data.changeset; + var thisAuthor = sessioninfos[client.id].author; var r, apool, pad; @@ -545,8 +546,6 @@ function handleUserChanges(client, message) return; } - var thisAuthor = sessioninfos[client.id].author; - pad.appendRevision(changeset, thisAuthor); var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); From f1b4206cadb716dd3c4b2c924e0b988431034263 Mon Sep 17 00:00:00 2001 From: Richard Braakman Date: Wed, 26 Sep 2012 03:01:24 +0300 Subject: [PATCH 11/12] Fix crash when client submits changeset based on too-old revision We had a problem with the server running out of stack space if a client submitted a changeset based on a revision more than about 1000 revs old. (944 was our cutoff but yours may vary). This happened in the wild with about 30 people editing via flaky wifi. A disconnected client would try to submit a fairly old changeset when reconnecting, and a few minutes was enough for 30 people to generate that many revs. The stack kept growing because pad.getRevisionChangeset was being answered from the cache, so no I/O interrupted the callback chain. (This was seen with mysql, I don't know about other backends.) This patch forces a nextTick every 200 revisions to solve this problem. --- src/node/handler/PadMessageHandler.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 160686804..6a462b019 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -526,7 +526,11 @@ function handleUserChanges(client, message) if(ERR(err, callback)) return; changeset = Changeset.follow(c, changeset, false, apool); - callback(null); + if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep + async.nextTick(callback); + } else { + callback(null); + } }); }, //use the callback of the series function From 7aaef01346ca61d7778c15f9b35f72713065f297 Mon Sep 17 00:00:00 2001 From: Richard Braakman Date: Thu, 27 Sep 2012 23:05:18 +0300 Subject: [PATCH 12/12] Prettify session handling in handleUserChanges Also add a comment to explain what's going on with thisSession. No changes in behavior. --- src/node/handler/PadMessageHandler.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 6a462b019..8a5a92bb6 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -465,7 +465,9 @@ function handleUserChanges(client, message) var baseRev = message.data.baseRev; var wireApool = (new AttributePool()).fromJsonable(message.data.apool); var changeset = message.data.changeset; - var thisAuthor = sessioninfos[client.id].author; + // The client might disconnect between our callbacks. We should still + // finish processing the changeset, so keep a reference to the session. + var thisSession = sessioninfos[client.id]; var r, apool, pad; @@ -473,7 +475,7 @@ function handleUserChanges(client, message) //get the pad function(callback) { - padManager.getPad(sessioninfos[client.id].padId, function(err, value) + padManager.getPad(thisSession.padId, function(err, value) { if(ERR(err, callback)) return; pad = value; @@ -550,7 +552,7 @@ function handleUserChanges(client, message) return; } - pad.appendRevision(changeset, thisAuthor); + pad.appendRevision(changeset, thisSession.author); var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); if (correctionChangeset) {