Show users editing and lag, properly send message to indicate that all users have left channel and show basic error box if disconnected.

pull/1/head
Caleb James DeLisle 10 years ago
parent 5ae599fa07
commit 6cbdcdec65

@ -103,7 +103,7 @@ var dropClient = function (ctx, userpass) {
var chanName = client.channels[i]; var chanName = client.channels[i];
var chan = ctx.channels[chanName]; var chan = ctx.channels[chanName];
var idx = chan.indexOf(client); var idx = chan.indexOf(client);
if (idx < 0) { throw new Error(); } if (idx < 0) { continue; }
console.log("Removing ["+client.userName+"] from channel ["+chanName+"]"); console.log("Removing ["+client.userName+"] from channel ["+chanName+"]");
chan.splice(idx, 1); chan.splice(idx, 1);
if (chan.length === 0) { if (chan.length === 0) {
@ -122,6 +122,7 @@ var handleMessage = function (ctx, socket, msg) {
msg = popPassword(msg); msg = popPassword(msg);
if (parsed.content[0] === REGISTER) { if (parsed.content[0] === REGISTER) {
if (parsed.user.length === 0) { throw new Error(); }
console.log("[" + userPass + "] registered"); console.log("[" + userPass + "] registered");
var client = ctx.registeredClients[userPass] = ctx.registeredClients[userPass] || { var client = ctx.registeredClients[userPass] = ctx.registeredClients[userPass] || {
channels: [parsed.channelId], channels: [parsed.channelId],
@ -131,17 +132,27 @@ console.log("[" + userPass + "] registered");
client.socket = socket; client.socket = socket;
var chan = ctx.channels[parsed.channelId] = ctx.channels[parsed.channelId] || []; var chan = ctx.channels[parsed.channelId] = ctx.channels[parsed.channelId] || [];
var newChan = (chan.length === 0);
chan.name = parsed.channelId; chan.name = parsed.channelId;
chan.push(client);
// we send a register ack right away but then we fallthrough // we send a register ack right away but then we fallthrough
// to let other users know that we were registered. // to let other users know that we were registered.
sendMsg(mkMessage('', parsed.channelId, [1,0]), socket); sendMsg(mkMessage('', parsed.channelId, [1,0]), socket);
var sendMsgs = function () {
sendChannelMessage(ctx, chan, msg, function () { sendChannelMessage(ctx, chan, msg, function () {
chan.push(client);
ctx.store.getMessages(chan.name, function (msg) { ctx.store.getMessages(chan.name, function (msg) {
sendMsg(msg, socket); sendMsg(msg, socket);
}); });
}); });
};
if (newChan) {
sendChannelMessage(ctx, chan, mkMessage('', chan.name, [DISCONNECT,0]), sendMsgs);
} else {
sendMsgs();
}
return;
} }
if (parsed.content[0] === PING) { if (parsed.content[0] === PING) {

@ -31,7 +31,7 @@ var insert = function (coll, channelName, content, cb) {
}; };
var getMessages = function (coll, channelName, cb) { var getMessages = function (coll, channelName, cb) {
coll.find({chan:channelName}).forEach(function (doc) { coll.find({chan:channelName}).sort( { _id: 1 } ).forEach(function (doc) {
cb(doc.msg); cb(doc.msg);
}, function (err) { }, function (err) {
if (!err) { return; } if (!err) { return; }

@ -21,6 +21,7 @@
"jquery": "~2.1.1", "jquery": "~2.1.1",
"tweetnacl": "~0.12.2", "tweetnacl": "~0.12.2",
"ckeditor": "~4.4.5", "ckeditor": "~4.4.5",
"requirejs": "~2.1.15" "requirejs": "~2.1.15",
"modalBox": "~1.0.2"
} }
} }

@ -933,6 +933,11 @@ var handleMessage = ChainPad.handleMessage = function (realtime, msgStr) {
} }
if (msg.messageType === Message.DISCONNECT) { if (msg.messageType === Message.DISCONNECT) {
if (msg.userName === '') {
realtime.userList = [];
userListChange(realtime);
return;
}
var idx = realtime.userList.indexOf(msg.userName); var idx = realtime.userList.indexOf(msg.userName);
if (Common.PARANOIA) { Common.assert(idx > -1); } if (Common.PARANOIA) { Common.assert(idx > -1); }
if (idx > -1) { if (idx > -1) {

@ -0,0 +1,91 @@
/*
* Copyright 2014 XWiki SAS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
require.config({
'shim': {
'bower/modalBox/modalBox-min': ['bower/jquery/dist/jquery.min'],
}
});
define([
'messages',
'bower/modalBox/modalBox-min'
], function (Messages) {
var STYLE = [
'<style>',
'.modalBox {',
' padding:5px;',
' border:1px solid #CCC;',
' background:#FFF;',
' height:500px;',
' width:700px;',
' display:none;',
'}',
'img.iw-closeImg {',
' width:24px;',
' height:24px',
'}',
'.modalFooter {',
' color:#FFF;',
' position:absolute;',
' bottom:0px',
'}',
'.modalFooter span {',
' cursor:pointer;',
'}',
'.iw-modalOverlay {',
' background:#000;',
' opacity:.5',
'}',
'</style>'
].join('');
var CONTENT = [
'<center><h2 class="errorType"></h2></center>',
'<br>',
'<p class="errorExplanation"></p>'
].join('');
var ERROR_ADDITIONAL = [
'<p class="errorMoreExplanation"></p>',
'<label for="errorBox_detailsBox" class="errorDetailsLabel"></label>',
'<textarea id="errorBox_detailsBox" class="errorData"></textarea>',
].join('');
var showError = function (errorType, docHtml, moreInfo) {
$('body').append('<div class="modalBox"></div>');
var $modalbox = $('.modalBox')
$modalbox.append(CONTENT + STYLE);
$modalbox.find('.errorType').text(Messages['errorBox_errorType_' + errorType]);
$modalbox.find('.errorExplanation').text(Messages['errorBox_errorExplanation_' + errorType]);
if (moreInfo) {
$modalbox.append(ERROR_ADDITIONAL);
$modalbox.find('.errorMoreExplanation').text(Messages.errorBox_moreExplanation);
$modalbox.find('.errorData').text(Messages['errorBox_' + errorType]);
}
$modalbox.modalBox({
onOpen: boxOpened,
onClose: function () { $('.modalBox').remove(); }
});
$('.iw-modalOverlay').css({'z-index':10000});
};
return {
show: showError
};
});

@ -1,29 +1,15 @@
define([ define([
'api/config?cb=' + Math.random().toString(16).substring(2), 'api/config?cb=' + Math.random().toString(16).substring(2),
'realtime-wysiwyg', 'realtime-wysiwyg',
'messages',
'bower/jquery/dist/jquery.min', 'bower/jquery/dist/jquery.min',
'bower/ckeditor/ckeditor', 'bower/ckeditor/ckeditor',
'bower/tweetnacl/nacl-fast.min', 'bower/tweetnacl/nacl-fast.min',
], function (Config, RTWysiwyg) { ], function (Config, RTWysiwyg, Messages) {
var Ckeditor = window.CKEDITOR; var Ckeditor = window.CKEDITOR;
var Nacl = window.nacl; var Nacl = window.nacl;
var $ = jQuery; var $ = jQuery;
var INITIAL_STATE = [
'<p>',
'This is <strong>CryptPad</strong>, the zero knowledge realtime collaborative editor.',
'<br>',
'What you type here is encrypted so only people who have the link can access it.',
'<br>',
'Even the server cannot see what you type.',
'</p>',
'<p>',
'<small>',
'<i>What you see here, what you hear here, when you leave here, let it stay here</i>',
'</small>',
'</p>',
].join('');
var module = { exports: {} }; var module = { exports: {} };
var parseKey = function (str) { var parseKey = function (str) {
@ -54,12 +40,11 @@ define([
editor.on('instanceReady', function () { editor.on('instanceReady', function () {
editor.execCommand('maximize'); editor.execCommand('maximize');
var ifr = window.ifr = $('iframe')[0]; var ifr = window.ifr = $('iframe')[0];
ifr.contentDocument.body.innerHTML = INITIAL_STATE; ifr.contentDocument.body.innerHTML = Messages.initialState;
var rtw = var rtw =
RTWysiwyg.start(Config.websocketURL, RTWysiwyg.start(Config.websocketURL,
userName(), userName(),
{},
Nacl.util.encodeBase64(key.lookupKey).substring(0,10), Nacl.util.encodeBase64(key.lookupKey).substring(0,10),
key.cryptKey); key.cryptKey);
editor.on('change', function () { rtw.onEvent(); }); editor.on('change', function () { rtw.onEvent(); });

@ -0,0 +1,33 @@
define(function () {
var out = {};
out.errorBox_errorType_disconnected = 'Connection Lost';
out.errorBox_errorExplanation_disconnected = [
'Lost connection to server, you may reconnect by reloading the page or review your work ',
'by clicking outside of this box.'
].join('');
out.editingAlone = 'Editing alone';
out.editingWithOneOtherPerson = 'Editing with one other person';
out.editingWith = 'Editing with';
out.otherPeople = 'other people';
out.disconnected = 'Disconnected';
out.lag = 'Lag';
out.initialState = [
'<p>',
'This is <strong>CryptPad</strong>, the zero knowledge realtime collaborative editor.',
'<br>',
'What you type here is encrypted so only people who have the link can access it.',
'<br>',
'Even the server cannot see what you type.',
'</p>',
'<p>',
'<small>',
'<i>What you see here, what you hear here, when you leave here, let it stay here</i>',
'</small>',
'</p>',
].join('');
return out;
});

@ -16,12 +16,17 @@
*/ */
define([ define([
'html-patcher', 'html-patcher',
'errorbox',
'messages',
'rangy', 'rangy',
'chainpad', 'chainpad',
'otaml', 'otaml',
'bower/jquery/dist/jquery.min', 'bower/jquery/dist/jquery.min',
'bower/tweetnacl/nacl-fast.min' 'bower/tweetnacl/nacl-fast.min'
], function (HTMLPatcher) { ], function (HTMLPatcher, ErrorBox, Messages) {
window.ErrorBox = ErrorBox;
var $ = window.jQuery; var $ = window.jQuery;
var Rangy = window.rangy; var Rangy = window.rangy;
Rangy.init(); Rangy.init();
@ -29,12 +34,8 @@ define([
var Otaml = window.Otaml; var Otaml = window.Otaml;
var Nacl = window.nacl; var Nacl = window.nacl;
var ErrorBox = {};
var PARANOIA = true; var PARANOIA = true;
var module = { exports: {} }; var module = { exports: {} };
/** /**
@ -106,26 +107,34 @@ var ErrorBox = {};
}; };
var isSocketDisconnected = function (socket, realtime) { var isSocketDisconnected = function (socket, realtime) {
return socket._socket.readyState === socket.CLOSING var sock = socket._socket;
|| socket._socket.readyState === socket.CLOSED return sock.readyState === sock.CLOSING
|| sock.readyState === sock.CLOSED
|| (realtime.getLag().waiting && realtime.getLag().lag > MAX_LAG_BEFORE_DISCONNECT); || (realtime.getLag().waiting && realtime.getLag().lag > MAX_LAG_BEFORE_DISCONNECT);
}; };
var updateUserList = function (myUserName, listElement, userList, messages) { var updateUserList = function (myUserName, listElement, userList) {
var meIdx = userList.indexOf(myUserName); var meIdx = userList.indexOf(myUserName);
if (meIdx === -1) { if (meIdx === -1) {
listElement.text(messages.disconnected); listElement.text(Messages.disconnected);
return; return;
} }
listElement.text(messages.editingWith + ' ' + (userList.length - 1) + ' people'); if (userList.length === 1) {
listElement.text(Messages.editingAlone);
} else if (userList.length === 2) {
listElement.text(Messages.editingWithOneOtherPerson);
} else {
listElement.text(Messages.editingWith + ' ' + (userList.length - 1) +
Messages.otherPeople);
}
}; };
var createUserList = function (realtime, myUserName, container, messages) { var createUserList = function (realtime, myUserName, container) {
var id = uid(); var id = uid();
$(container).prepend('<div class="' + USER_LIST_CLS + '" id="'+id+'"></div>'); $(container).prepend('<div class="' + USER_LIST_CLS + '" id="'+id+'"></div>');
var listElement = $('#'+id); var listElement = $('#'+id);
realtime.onUserListChange(function (userList) { realtime.onUserListChange(function (userList) {
updateUserList(myUserName, listElement, userList, messages); updateUserList(myUserName, listElement, userList);
}); });
return listElement; return listElement;
}; };
@ -269,10 +278,10 @@ var ErrorBox = {};
return 'rtwysiwyg-uid-' + String(Math.random()).substring(2); return 'rtwysiwyg-uid-' + String(Math.random()).substring(2);
}; };
var checkLag = function (realtime, lagElement, messages) { var checkLag = function (realtime, lagElement) {
var lag = realtime.getLag(); var lag = realtime.getLag();
var lagSec = lag.lag/1000; var lagSec = lag.lag/1000;
var lagMsg = messages.lag + ' '; var lagMsg = Messages.lag + ' ';
if (lag.waiting && lagSec > 1) { if (lag.waiting && lagSec > 1) {
lagMsg += "?? " + Math.floor(lagSec); lagMsg += "?? " + Math.floor(lagSec);
} else { } else {
@ -281,12 +290,12 @@ var ErrorBox = {};
lagElement.text(lagMsg); lagElement.text(lagMsg);
}; };
var createLagElement = function (socket, realtime, container, messages) { var createLagElement = function (socket, realtime, container) {
var id = uid(); var id = uid();
$(container).append('<div class="' + LAG_ELEM_CLS + '" id="'+id+'"></div>'); $(container).append('<div class="' + LAG_ELEM_CLS + '" id="'+id+'"></div>');
var lagElement = $('#'+id); var lagElement = $('#'+id);
var intr = setInterval(function () { var intr = setInterval(function () {
checkLag(realtime, lagElement, messages); checkLag(realtime, lagElement);
}, 3000); }, 3000);
socket.onClose.push(function () { clearTimeout(intr); }); socket.onClose.push(function () { clearTimeout(intr); });
return lagElement; return lagElement;
@ -306,11 +315,11 @@ var ErrorBox = {};
'.' + TOOLBAR_CLS + ' {', '.' + TOOLBAR_CLS + ' {',
' color: #666;', ' color: #666;',
' font-weight: bold;', ' font-weight: bold;',
' background-color: #f0f0ee;', // ' background-color: #f0f0ee;',
' border-bottom: 1px solid #DDD;', // ' border-bottom: 1px solid #DDD;',
' border-top: 3px solid #CCC;', // ' border-top: 3px solid #CCC;',
' border-right: 2px solid #CCC;', // ' border-right: 2px solid #CCC;',
' border-left: 2px solid #CCC;', // ' border-left: 2px solid #CCC;',
' height: 26px;', ' height: 26px;',
' margin-bottom: -3px;', ' margin-bottom: -3px;',
' display: inline-block;', ' display: inline-block;',
@ -319,7 +328,7 @@ var ErrorBox = {};
'.' + TOOLBAR_CLS + ' div {', '.' + TOOLBAR_CLS + ' div {',
' padding: 0 10px;', ' padding: 0 10px;',
' height: 1.5em;', ' height: 1.5em;',
' background: #f0f0ee;', // ' background: #f0f0ee;',
' line-height: 25px;', ' line-height: 25px;',
' height: 22px;', ' height: 22px;',
'}', '}',
@ -338,6 +347,7 @@ var ErrorBox = {};
'.' + DEBUG_LINK_CLS + ':link { color:transparent; }', '.' + DEBUG_LINK_CLS + ':link { color:transparent; }',
'.' + DEBUG_LINK_CLS + ':link:hover { color:blue; }', '.' + DEBUG_LINK_CLS + ':link:hover { color:blue; }',
'.gwt-TabPanelBottom { border-top: 0 none; }', '.gwt-TabPanelBottom { border-top: 0 none; }',
'</style>' '</style>'
].join('\n')); ].join('\n'));
return toolbar; return toolbar;
@ -417,8 +427,7 @@ var ErrorBox = {};
return spl[0] + res.length + ':' + res; return spl[0] + res.length + ':' + res;
}; };
var start = module.exports.start = var start = module.exports.start = function (websocketUrl, userName, channel, cryptKey)
function (websocketUrl, userName, messages, channel, cryptKey)
{ {
var passwd = 'y'; var passwd = 'y';
var wysiwygDiv = document.getElementById('cke_1_contents'); var wysiwygDiv = document.getElementById('cke_1_contents');
@ -427,10 +436,11 @@ var ErrorBox = {};
var socket = makeWebsocket(websocketUrl); var socket = makeWebsocket(websocketUrl);
var onEvent = function () { }; var onEvent = function () { };
var toolbar = createRealtimeToolbar('#xwikieditcontent'); var toolbar = createRealtimeToolbar('#cke_1_toolbox');
socket.onClose.push(function () { socket.onClose.push(function () {
$(toolbar).remove(); $(toolbar).remove();
checkSocket();
}); });
var allMessages = []; var allMessages = [];
@ -474,17 +484,15 @@ var ErrorBox = {};
getDocHTML(doc), getDocHTML(doc),
{ transformFunction: Otaml.transform }); { transformFunction: Otaml.transform });
//createDebugLink(realtime, doc, allMessages, toolbar, messages); //createDebugLink(realtime, doc, allMessages, toolbar);
createLagElement(socket, createLagElement(socket,
realtime, realtime,
toolbar.find('.rtwysiwyg-toolbar-rightside'), toolbar.find('.rtwysiwyg-toolbar-rightside'));
messages);
createUserList(realtime, createUserList(realtime,
userName, userName,
toolbar.find('.rtwysiwyg-toolbar-leftside'), toolbar.find('.rtwysiwyg-toolbar-leftside'));
messages);
onEvent = function () { onEvent = function () {
if (isErrorState) { return; } if (isErrorState) { return; }
@ -522,11 +530,10 @@ var ErrorBox = {};
}; };
realtime.onUserListChange(function (userList) { realtime.onUserListChange(function (userList) {
if (!initializing && userList.indexOf(userName) === -1) { return; } if (!initializing || userList.indexOf(userName) === -1) { return; }
// if we spot ourselves being added to the document, we'll switch // if we spot ourselves being added to the document, we'll switch
// 'initializing' off because it means we're fully synced. // 'initializing' off because it means we're fully synced.
initializing = false; initializing = false;
userDocBeforePatch = realtime.getUserDoc();
incomingPatch(); incomingPatch();
}); });

Loading…
Cancel
Save