summaryrefslogtreecommitdiffstats
path: root/trunk/etherpad/src/static/js/collab_client.js
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/etherpad/src/static/js/collab_client.js')
-rw-r--r--trunk/etherpad/src/static/js/collab_client.js628
1 files changed, 0 insertions, 628 deletions
diff --git a/trunk/etherpad/src/static/js/collab_client.js b/trunk/etherpad/src/static/js/collab_client.js
deleted file mode 100644
index d8834d7..0000000
--- a/trunk/etherpad/src/static/js/collab_client.js
+++ /dev/null
@@ -1,628 +0,0 @@
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-$(window).bind("load", function() {
- getCollabClient.windowLoaded = true;
-});
-
-/** 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.
- "serverVars" are from calling doc.getCollabClientVars() on the server. */
-function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
- var editor = ace2editor;
-
- var rev = serverVars.rev;
- var padId = serverVars.padId;
- var globalPadId = serverVars.globalPadId;
-
- var state = "IDLE";
- var stateMessage;
- var stateMessageSocketId;
- var channelState = "CONNECTING";
- var appLevelDisconnectReason = null;
-
- var lastCommitTime = 0;
- var initialStartConnectTime = 0;
-
- var userId = initialUserInfo.userId;
- var socketId;
- var socket;
- var userSet = {}; // userId -> userInfo
- userSet[userId] = initialUserInfo;
-
- var reconnectTimes = [];
- var caughtErrors = [];
- var caughtErrorCatchers = [];
- var caughtErrorTimes = [];
- var debugMessages = [];
-
- tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData);
- tellAceActiveAuthorInfo(initialUserInfo);
-
- var callbacks = {
- onUserJoin: function() {},
- onUserLeave: function() {},
- onUpdateUserInfo: function() {},
- onChannelStateChange: function() {},
- onClientMessage: function() {},
- onInternalAction: function() {},
- onConnectionTrouble: function() {},
- onServerMessage: function() {}
- };
-
- $(window).bind("unload", function() {
- if (socket) {
- socket.onclosed = function() {};
- socket.onhiccup = function() {};
- socket.disconnect(true);
- }
- });
- if ($.browser.mozilla) {
- // Prevent "escape" from taking effect and canceling a comet connection;
- // doesn't work if focus is on an iframe.
- $(window).bind("keydown", function(evt) { if (evt.which == 27) { evt.preventDefault() } });
- }
-
- editor.setProperty("userAuthor", userId);
- editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool);
- editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges));
-
- function abandonConnection(reason) {
- if (socket) {
- socket.onclosed = function() {};
- socket.onhiccup = function() {};
- socket.disconnect();
- }
- socket = null;
- setChannelState("DISCONNECTED", reason);
- }
-
- function dmesg(str) {
- if (typeof window.ajlog == "string") window.ajlog += str+'\n';
- debugMessages.push(str);
- }
-
- function handleUserChanges() {
- if ((! socket) || channelState == "CONNECTING") {
- if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000)) {
- abandonConnection("initsocketfail"); // give up
- }
- else {
- // check again in a bit
- setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges),
- 1000);
- }
- return;
- }
-
- var t = (+new Date());
-
- if (state != "IDLE") {
- if (state == "COMMITTING" && (t - lastCommitTime) > 20000) {
- // a commit is taking too long
- appLevelDisconnectReason = "slowcommit";
- socket.disconnect();
- }
- else if (state == "COMMITTING" && (t - lastCommitTime) > 5000) {
- callbacks.onConnectionTrouble("SLOW");
- }
- else {
- // run again in a few seconds, to detect a disconnect
- setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges),
- 3000);
- }
- return;
- }
-
- var earliestCommit = lastCommitTime + 500;
- if (t < earliestCommit) {
- setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges),
- earliestCommit - t);
- return;
- }
-
- var sentMessage = false;
- var userChangesData = editor.prepareUserChangeset();
- if (userChangesData.changeset) {
- lastCommitTime = t;
- state = "COMMITTING";
- stateMessage = {type:"USER_CHANGES", baseRev:rev,
- changeset:userChangesData.changeset,
- apool: userChangesData.apool };
- stateMessageSocketId = socketId;
- sendMessage(stateMessage);
- sentMessage = true;
- callbacks.onInternalAction("commitPerformed");
- }
-
- if (sentMessage) {
- // run again in a few seconds, to detect a disconnect
- setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges),
- 3000);
- }
- }
-
- function getStats() {
- var stats = {};
-
- stats.screen = [$(window).width(), $(window).height(),
- window.screen.availWidth, window.screen.availHeight,
- window.screen.width, window.screen.height].join(',');
- stats.ip = serverVars.clientIp;
- stats.useragent = serverVars.clientAgent;
-
- return stats;
- }
-
- function setUpSocket() {
- var success = false;
- callCatchingErrors("setUpSocket", function() {
- appLevelDisconnectReason = null;
-
- var oldSocketId = socketId;
- socketId = String(Math.floor(Math.random()*1e12));
- socket = new WebSocket(socketId);
- socket.onmessage = wrapRecordingErrors("socket.onmessage", handleMessageFromServer);
- socket.onclosed = wrapRecordingErrors("socket.onclosed", handleSocketClosed);
- socket.onopen = wrapRecordingErrors("socket.onopen", function() {
- hiccupCount = 0;
- setChannelState("CONNECTED");
- var msg = { type:"CLIENT_READY", roomType:'padpage',
- roomName:'padpage/'+globalPadId,
- data: {
- lastRev:rev,
- userInfo:userSet[userId],
- stats: getStats() } };
- if (oldSocketId) {
- msg.data.isReconnectOf = oldSocketId;
- msg.data.isCommitPending = (state == "COMMITTING");
- }
- sendMessage(msg);
- doDeferredActions();
- });
- socket.onhiccup = wrapRecordingErrors("socket.onhiccup", handleCometHiccup);
- socket.onlogmessage = dmesg;
- socket.connect();
- success = true;
- });
- if (success) {
- initialStartConnectTime = +new Date();
- }
- else {
- abandonConnection("initsocketfail");
- }
- }
- function setUpSocketWhenWindowLoaded() {
- if (getCollabClient.windowLoaded) {
- setUpSocket();
- }
- else {
- setTimeout(setUpSocketWhenWindowLoaded, 200);
- }
- }
- setTimeout(setUpSocketWhenWindowLoaded, 0);
-
- var hiccupCount = 0;
- function handleCometHiccup(params) {
- dmesg("HICCUP (connected:"+(!!params.connected)+")");
- var connectedNow = params.connected;
- if (! connectedNow) {
- hiccupCount++;
- // skip first "cut off from server" notification
- if (hiccupCount > 1) {
- setChannelState("RECONNECTING");
- }
- }
- else {
- hiccupCount = 0;
- setChannelState("CONNECTED");
- }
- }
-
- function sendMessage(msg) {
- socket.postMessage(JSON.stringify({type: "COLLABROOM", 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;
- }
- };
- }
-
- function callCatchingErrors(catcher, func) {
- try {
- wrapRecordingErrors(catcher, func)();
- }
- catch (e) { /*absorb*/ }
- }
-
- function handleMessageFromServer(evt) {
- if (! socket) return;
- if (! evt.data) return;
- var wrapper = JSON.parse(evt.data);
- if(wrapper.type != "COLLABROOM") return;
- var 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 (newRev != (rev+1)) {
- dmesg("bad message revision on NEW_CHANGES: "+newRev+" not "+(rev+1));
- socket.disconnect();
- return;
- }
- rev = newRev;
- editor.applyChangesToBase(changeset, author, apool);
- }
- else if (msg.type == "ACCEPT_COMMIT") {
- var newRev = msg.newRev;
- if (newRev != (rev+1)) {
- dmesg("bad message revision on ACCEPT_COMMIT: "+newRev+" not "+(rev+1));
- socket.disconnect();
- return;
- }
- rev = newRev;
- editor.applyPreparedChangesetToBase();
- setStateIdle();
- callCatchingErrors("onInternalAction", function() {
- callbacks.onInternalAction("commitAcceptedByServer");
- });
- callCatchingErrors("onConnectionTrouble", function() {
- callbacks.onConnectionTrouble("OK");
- });
- handleUserChanges();
- }
- 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;
- if (userSet[id]) {
- userSet[id] = userInfo;
- callbacks.onUpdateUserInfo(userInfo);
- dmesgUsers();
- }
- else {
- userSet[id] = userInfo;
- callbacks.onUserJoin(userInfo);
- dmesgUsers();
- }
- tellAceActiveAuthorInfo(userInfo);
- }
- else if (msg.type == "USER_LEAVE") {
- var userInfo = msg.userInfo;
- var id = userInfo.userId;
- if (userSet[id]) {
- delete userSet[userInfo.userId];
- fadeAceAuthorInfo(userInfo);
- callbacks.onUserLeave(userInfo);
- dmesgUsers();
- }
- }
- else if (msg.type == "DISCONNECT_REASON") {
- appLevelDisconnectReason = msg.reason;
- }
- else if (msg.type == "CLIENT_MESSAGE") {
- callbacks.onClientMessage(msg.payload);
- }
- else if (msg.type == "SERVER_MESSAGE") {
- callbacks.onServerMessage(msg.payload);
- }
- }
- function updateUserInfo(userInfo) {
- userInfo.userId = userId;
- userSet[userId] = userInfo;
- tellAceActiveAuthorInfo(userInfo);
- if (! socket) return;
- sendMessage({type: "USERINFO_UPDATE", userInfo:userInfo});
- }
-
- function tellAceActiveAuthorInfo(userInfo) {
- tellAceAuthorInfo(userInfo.userId, userInfo.colorId);
- }
- function tellAceAuthorInfo(userId, colorId, inactive) {
- if (colorId || (typeof colorId) == "number") {
- colorId = Number(colorId);
- if (options && options.colorPalette && options.colorPalette[colorId]) {
- var cssColor = options.colorPalette[colorId];
- if (inactive) {
- editor.setAuthorInfo(userId, {bgcolor: cssColor, fade: 0.5});
- }
- else {
- editor.setAuthorInfo(userId, {bgcolor: cssColor});
- }
- }
- }
- }
- function fadeAceAuthorInfo(userInfo) {
- tellAceAuthorInfo(userInfo.userId, userInfo.colorId, true);
- }
-
- function getConnectedUsers() {
- return valuesArray(userSet);
- }
-
- function tellAceAboutHistoricalAuthors(hadata) {
- for(var author in hadata) {
- var data = hadata[author];
- if (! userSet[author]) {
- tellAceAuthorInfo(author, data.colorId, true);
- }
- }
- }
-
- function dmesgUsers() {
- //pad.dmesg($.map(getConnectedUsers(), function(u) { return u.userId.slice(-2); }).join(','));
- }
-
- function handleSocketClosed(params) {
- socket = null;
-
- $.each(keys(userSet), function() {
- var uid = String(this);
- if (uid != userId) {
- var userInfo = userSet[uid];
- delete userSet[uid];
- callbacks.onUserLeave(userInfo);
- dmesgUsers();
- }
- });
-
- var reason = appLevelDisconnectReason || params.reason;
- var shouldReconnect = params.reconnect;
- if (shouldReconnect) {
-
- // determine if this is a tight reconnect loop due to weird connectivity problems
- reconnectTimes.push(+new Date());
- var TOO_MANY_RECONNECTS = 8;
- var TOO_SHORT_A_TIME_MS = 10000;
- if (reconnectTimes.length >= TOO_MANY_RECONNECTS &&
- ((+new Date()) - reconnectTimes[reconnectTimes.length-TOO_MANY_RECONNECTS]) <
- TOO_SHORT_A_TIME_MS) {
- setChannelState("DISCONNECTED", "looping");
- }
- else {
- setChannelState("RECONNECTING", reason);
- setUpSocket();
- }
-
- }
- else {
- setChannelState("DISCONNECTED", reason);
- }
- }
-
- function setChannelState(newChannelState, moreInfo) {
- if (newChannelState != channelState) {
- channelState = newChannelState;
- callbacks.onChannelStateChange(channelState, moreInfo);
- }
- }
-
- function keys(obj) {
- var array = [];
- $.each(obj, function (k, v) { array.push(k); });
- return array;
- }
- function valuesArray(obj) {
- var array = [];
- $.each(obj, function (k, v) { array.push(v); });
- return array;
- }
-
- // We need to present a working interface even before the socket
- // is connected for the first time.
- var deferredActions = [];
- function defer(func, tag) {
- return function() {
- var that = this;
- var args = arguments;
- function action() {
- func.apply(that, args);
- }
- action.tag = tag;
- if (channelState == "CONNECTING") {
- deferredActions.push(action);
- }
- else {
- action();
- }
- }
- }
- function doDeferredActions(tag) {
- var newArray = [];
- for(var i=0;i<deferredActions.length;i++) {
- var a = deferredActions[i];
- if ((!tag) || (tag == a.tag)) {
- a();
- }
- else {
- newArray.push(a);
- }
- }
- deferredActions = newArray;
- }
-
- function sendClientMessage(msg) {
- sendMessage({ type: "CLIENT_MESSAGE", payload: msg });
- }
-
- function getCurrentRevisionNumber() {
- return rev;
- }
-
- function getDiagnosticInfo() {
- var maxCaughtErrors = 3;
- var maxAceErrors = 3;
- var maxDebugMessages = 50;
- var longStringCutoff = 500;
-
- function trunc(str) {
- return String(str).substring(0, longStringCutoff);
- }
-
- var info = { errors: {length: 0} };
- function addError(e, catcher, time) {
- var error = {catcher:catcher};
- if (time) error.time = time;
-
- // a little over-cautious?
- try { if (e.description) error.description = e.description; } catch (x) {}
- try { if (e.fileName) error.fileName = e.fileName; } catch (x) {}
- try { if (e.lineNumber) error.lineNumber = e.lineNumber; } catch (x) {}
- try { if (e.message) error.message = e.message; } catch (x) {}
- try { if (e.name) error.name = e.name; } catch (x) {}
- try { if (e.number) error.number = e.number; } catch (x) {}
- try { if (e.stack) error.stack = trunc(e.stack); } catch (x) {}
-
- info.errors[info.errors.length] = error;
- info.errors.length++;
- }
- for(var i=0; ((i<caughtErrors.length) && (i<maxCaughtErrors)); i++) {
- addError(caughtErrors[i], caughtErrorCatchers[i], caughtErrorTimes[i]);
- }
- if (editor) {
- var aceErrors = editor.getUnhandledErrors();
- for(var i=0; ((i<aceErrors.length) && (i<maxAceErrors)) ;i++) {
- var errorRecord = aceErrors[i];
- addError(errorRecord.error, "ACE", errorRecord.time);
- }
- }
-
- info.time = +new Date();
- info.collabState = state;
- info.channelState = channelState;
- info.lastCommitTime = lastCommitTime;
- info.numSocketReconnects = reconnectTimes.length;
- info.userId = userId;
- info.currentRev = rev;
- info.participants = (function() {
- var pp = [];
- for(var u in userSet) {
- pp.push(u);
- }
- return pp.join(',');
- })();
-
- if (debugMessages.length > maxDebugMessages) {
- debugMessages = debugMessages.slice(debugMessages.length-maxDebugMessages,
- debugMessages.length);
- }
-
- info.debugMessages = {length: 0};
- for(var i=0;i<debugMessages.length;i++) {
- info.debugMessages[i] = trunc(debugMessages[i]);
- info.debugMessages.length++;
- }
-
- return info;
- }
-
- function getMissedChanges() {
- var obj = {};
- obj.userInfo = userSet[userId];
- obj.baseRev = rev;
- if (state == "COMMITTING" && stateMessage) {
- obj.committedChangeset = stateMessage.changeset;
- obj.committedChangesetAPool = stateMessage.apool;
- obj.committedChangesetSocketId = stateMessageSocketId;
- editor.applyPreparedChangesetToBase();
- }
- var userChangesData = editor.prepareUserChangeset();
- if (userChangesData.changeset) {
- obj.furtherChangeset = userChangesData.changeset;
- obj.furtherChangesetAPool = userChangesData.apool;
- }
- return obj;
- }
-
- function setStateIdle() {
- state = "IDLE";
- callbacks.onInternalAction("newlyIdle");
- schedulePerhapsCallIdleFuncs();
- }
-
- function callWhenNotCommitting(func) {
- idleFuncs.push(func);
- schedulePerhapsCallIdleFuncs();
- }
-
- var idleFuncs = [];
- function schedulePerhapsCallIdleFuncs() {
- setTimeout(function() {
- if (state == "IDLE") {
- while (idleFuncs.length > 0) {
- var f = idleFuncs.shift();
- f();
- }
- }
- }, 0);
- }
-
- var self;
- return (self = {
- setOnUserJoin: function(cb) { callbacks.onUserJoin = cb; },
- setOnUserLeave: function(cb) { callbacks.onUserLeave = cb; },
- setOnUpdateUserInfo: function(cb) { callbacks.onUpdateUserInfo = cb; },
- setOnChannelStateChange: function(cb) { callbacks.onChannelStateChange = cb; },
- setOnClientMessage: function(cb) { callbacks.onClientMessage = cb; },
- setOnInternalAction: function(cb) { callbacks.onInternalAction = cb; },
- setOnConnectionTrouble: function(cb) { callbacks.onConnectionTrouble = cb; },
- setOnServerMessage: function(cb) { callbacks.onServerMessage = cb; },
- updateUserInfo: defer(updateUserInfo),
- getConnectedUsers: getConnectedUsers,
- sendClientMessage: sendClientMessage,
- getCurrentRevisionNumber: getCurrentRevisionNumber,
- getDiagnosticInfo: getDiagnosticInfo,
- getMissedChanges: getMissedChanges,
- callWhenNotCommitting: callWhenNotCommitting,
- addHistoricalAuthors: tellAceAboutHistoricalAuthors
- });
-}
-
-function selectElementContents(elem) {
- if ($.browser.msie) {
- var range = document.body.createTextRange();
- range.moveToElementText(elem);
- range.select();
- }
- else {
- if (window.getSelection) {
- var browserSelection = window.getSelection();
- if (browserSelection) {
- var range = document.createRange();
- range.selectNodeContents(elem);
- browserSelection.removeAllRanges();
- browserSelection.addRange(range);
- }
- }
- }
-}