| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922 |
- function P2PCollab(ui, sync, channelId)
- {
- var graph = ui.editor.graph;
- var encrypted = true; // global flag to encrypt all messages
- var sessionCount = 0;
- var socket = null;
- var colors = [
- //White font
- '#e6194b', '#3cb44b', '#4363d8', '#f58231', '#911eb4',
- '#f032e6', '#469990', '#9A6324', '#800000', '#808000',
- '#000075', '#a9a9a9',
- //Black font
- '#ffe119', '#42d4f4', '#bfef45', '#fabed4', '#dcbeff',
- '#fffac8', '#aaffc3', '#ffd8b1'
- ];
- var connectedSessions = {}, messageId = 1, clientLastMsgId = {}, clientsToSessions = {},
- connectedClient = {}, sessionColors = {};
- var myClientId, newClients = {}, p2pClients = {}, useSocket = true, fileJoined = false, destroyed = false;
- var INACTIVE_TIMEOUT = 120000; //2 min
- var SELECTION_OPACITY = 70; //The default opacity of 30 is not visible enough with all colors
- var cursorDelay = 300;
- // TODO: Avoid negation, move to Editor.ENABLE_P2P and use p2p=1 URL parameter
- // add to Editor.configure
- var NO_P2P = urlParams['no-p2p'] != '0';
- var joinInProgress = false, joinId = 0;
- var lastError = null;
-
- var sendReply = mxUtils.bind(this, function(action, msg)
- {
- if (destroyed) return;
- try
- {
- if (socket != null)
- {
- socket.send(JSON.stringify({action: action, msg: msg}));
- if (!NO_P2P)
- {
- EditorUi.debug('P2PCollab: sending to socket server', [action], [msg]);
- }
- }
- else
- {
- this.joinFile(true);
- }
- }
- catch (e)
- {
- lastError = e;
- sync.file.fireEvent(new mxEventObject('realtimeStateChanged'));
- EditorUi.debug('P2PCollab:', 'sendReply error', arguments, e);
- }
- });
- function createCursorImage(color)
- {
- return Graph.createSvgImage(8, 12, '<path d="M 4 0 L 8 12 L 4 10 L 0 12 Z" stroke="'+ color +'" fill="'+ color +'"/>').src;
- };
- function sendMessage(type, data)
- {
- try
- {
- if (destroyed) return;
- var user = sync.file.getCurrentUser();
- if (!fileJoined || user == null || user.displayName == null) return;
-
- //Converting to a string such that webRTC works also
- var msg = {from: myClientId, id: messageId,
- type: type, sessionId: sync.clientId, userId: user.id,
- username: user.displayName, data: data,
- protocol: DrawioFileSync.PROTOCOL,
- editor: EditorUi.VERSION};
- if (encrypted)
- {
- // data is needed for old server to not drop messages
- msg = {bytes: sync.objectToString(msg), data: 'aes'};
- }
- msg = JSON.stringify(msg);
-
- if (NO_P2P && type != 'cursor')
- {
- EditorUi.debug('P2PCollab: sending to socket server', [msg]);
- }
- messageId++;
- var p2pOnlyMsgs = !NO_P2P && (type == 'cursor' || type == 'selectionChange');
- if (useSocket && !p2pOnlyMsgs)
- {
- sendReply('message', msg);
- }
-
- //TODO Currently, we only send cursor & selection messages via P2P
- if (p2pOnlyMsgs)
- {
- for (p2pId in p2pClients)
- {
- p2pClients[p2pId].send(msg);
- }
- }
- }
- catch (e)
- {
- if (window.console != null)
- {
- console.error(e, type, data);
- }
- }
- };
-
- this.sendMessage = sendMessage;
-
- this.sendDiff = function(msg)
- {
- this.sendMessage('diff', (encrypted) ?
- {diff: msg} : {patch: encodeURIComponent(
- sync.objectToString(msg))});
- };
- this.sendNotification = function(msg)
- {
- this.sendMessage('notify', (encrypted) ?
- {msg: msg} : {data: encodeURIComponent(
- sync.objectToString(msg))});
- };
- this.getState = function()
- {
- return socket != null ? socket.readyState : 3 /* CLOSED */;
- };
- this.getLastError = function()
- {
- return lastError;
- };
- function debounce(func, wait)
- {
- var timeout, lastInvocation = -1;
- return function()
- {
- clearTimeout(timeout);
- var context = this, args = arguments;
- var later = function()
- {
- timeout = null;
- lastInvocation = Date.now();
- func.apply(context, args);
- };
- if (Date.now() - lastInvocation > wait)
- {
- later();
- }
- else
- {
- timeout = setTimeout(later, wait);
- }
- };
- };
- function sendCursor(me)
- {
- if (ui.shareCursorPosition && !graph.isMouseDown)
- {
- var offset = mxUtils.getOffset(graph.container);
- var tr = graph.view.translate;
- var s = graph.view.scale;
- var pageId = (ui.currentPage != null) ?
- ui.currentPage.getId() : null;
- sendMessage('cursor', {pageId: pageId,
- x: Math.round((me.getX() - offset.x +
- graph.container.scrollLeft) / s - tr.x),
- y: Math.round((me.getY() - offset.y +
- graph.container.scrollTop) / s - tr.y)});
- }
- };
- this.mouseListeners = {
- startX: 0,
- startY: 0,
- scrollLeft: 0,
- scrollTop: 0,
- mouseDown: function(sender, me) {},
- mouseMove: debounce(function(sender, me)
- {
- sendCursor(me);
- }, cursorDelay), // 5 frame/sec approx TODO with 100 milli (10 fps), the cursor is smoother
- mouseUp: function(sender, me)
- {
- sendCursor(me);
- }
- };
- graph.addMouseListener(this.mouseListeners);
- this.shareCursorPositionListener = function()
- {
- if (!ui.isShareCursorPosition())
- {
- sendMessage('cursor', {hide: true});
- }
- };
- ui.addListener('shareCursorPositionChanged', this.shareCursorPositionListener);
- // Clears remote selection state for large selections
- var selectionLimit = mxGraphHandler.prototype.maxCells;
- var updateThread = null;
- var lastSelection = {};
-
- this.selectionChangeListener = function(sender, evt)
- {
- var mapToIds = function(c)
- {
- return (c != null) ? c.id : null;
- };
-
- if (updateThread != null)
- {
- window.clearTimeout(updateThread);
- }
- updateThread = window.setTimeout(function()
- {
- var selection = (graph.getSelectionCount() > selectionLimit) ?
- [] : graph.getSelectionCells().map(mapToIds)
- var pageId = (ui.currentPage != null) ?
- ui.currentPage.getId() : null;
- // Computes diff between last and current selection
- var newSelection = {};
- var removed = [];
- var added = [];
- for (var i = 0; i < selection.length; i++)
- {
- var id = selection[i];
- if (id != null)
- {
- newSelection[id] = true;
- if (lastSelection[id] == null)
- {
- added.push(id);
- }
- }
- }
- for (var id in lastSelection)
- {
- if (!newSelection[id])
- {
- removed.push(id);
- }
- }
-
- lastSelection = newSelection;
- sendMessage('selectionChange', {pageId: pageId,
- removed: removed, added: added});
- }, 300);
- };
- graph.getSelectionModel().addListener(mxEvent.CHANGE, this.selectionChangeListener);
- function updateCursor(entry, transition)
- {
- var pageId = (ui.currentPage != null) ?
- ui.currentPage.getId() : null;
-
- if (entry != null && entry.cursor != null &&
- entry.lastCursor != null)
- {
- if (entry.lastCursor.hide != null ||
- !ui.isShowRemoteCursors() ||
- (entry.lastCursor.pageId != null &&
- entry.lastCursor.pageId != pageId))
- {
- entry.cursor.style.display = 'none';
- }
- else
- {
- var tr = graph.view.translate;
- var s = graph.view.scale;
- var x = ((tr.x + entry.lastCursor.x) * s) + 8;
- var y = ((tr.y + entry.lastCursor.y) * s) - 12;
- var img = entry.cursor.getElementsByTagName('img')[0];
- function setPosition()
- {
- var cx = Math.max(graph.container.scrollLeft, Math.min(graph.container.scrollLeft +
- graph.container.clientWidth - entry.cursor.clientWidth, x));
- var cy = Math.max(graph.container.scrollTop - 22, Math.min(graph.container.scrollTop +
- graph.container.clientHeight - entry.cursor.clientHeight, y));
- img.style.opacity = (cx != x || cy != y) ? 0 : 1;
- entry.cursor.style.left = cx + 'px';
- entry.cursor.style.top = cy + 'px';
- entry.cursor.style.display = '';
- };
- if (transition)
- {
- mxUtils.setPrefixedStyle(entry.cursor.style, 'transition', 'all ' + (3 * cursorDelay) + 'ms ease-out');
- mxUtils.setPrefixedStyle(img.style, 'transition', 'all ' + (3 * cursorDelay) + 'ms ease-out');
- window.setTimeout(setPosition, 0);
- }
- else
- {
- mxUtils.setPrefixedStyle(entry.cursor.style, 'transition', null);
- mxUtils.setPrefixedStyle(img.style, 'transition', null);
- setPosition();
- }
- }
- }
- };
- this.cursorHandler = mxUtils.bind(this, function()
- {
- for (var key in connectedSessions)
- {
- updateCursor(connectedSessions[key]);
- }
- });
- mxEvent.addListener(graph.container, 'scroll', this.cursorHandler);
- graph.getView().addListener(mxEvent.SCALE, this.cursorHandler);
- graph.getView().addListener(mxEvent.TRANSLATE, this.cursorHandler);
- graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.cursorHandler);
- ui.addListener('showRemoteCursorsChanged', this.cursorHandler);
- ui.editor.addListener('pageSelected', this.cursorHandler);
- function processMsg(msg, fromCId)
- {
- try
- {
- if (destroyed) return;
- msg = JSON.parse(msg);
- if (msg.bytes != null)
- {
- msg = sync.stringToObject(msg.bytes);
- }
-
- if (NO_P2P && msg.type != 'cursor')
- {
- EditorUi.debug('P2PCollab: msg received', [msg]);
- }
- //Exclude P2P messages from duplicate messages test since p2p can arrive before socket and interrupt delivery
- if (fromCId != null)
- {
- //Safeguard from duplicate messages or receiving my own messages
- if (msg.from == myClientId || clientLastMsgId[msg.from] >= msg.id)
- {
- EditorUi.debug('P2PCollab: Dropped Message', msg, myClientId, clientLastMsgId[msg.from])
- return;
- }
-
- clientLastMsgId[msg.from] = msg.id;
- }
-
- var username = msg.username? msg.username : 'Anonymous';
- var sessionId = msg.sessionId;
- var cursor, selection;
- function createCursor()
- {
- if (connectedSessions[sessionId] == null)
- {
- var clrIndex = sessionColors[sessionId];
- if (clrIndex == null)
- {
- clrIndex = sessionCount % colors.length;
- sessionColors[sessionId] = clrIndex;
- sessionCount++;
- }
- var clr = colors[clrIndex];
- var lblClr = clrIndex > 11? 'black' : 'white';
- connectedSessions[sessionId] = {
- cursor: document.createElement('div'),
- color: clr,
- selection: {}
- };
-
- clientsToSessions[fromCId] = sessionId;
- cursor = connectedSessions[sessionId].cursor;
-
- cursor.style.pointerEvents = 'none';
- cursor.style.position = 'absolute';
- cursor.style.display = 'none';
- cursor.style.opacity = '0.9';
- var img = document.createElement('img');
- mxUtils.setPrefixedStyle(img.style, 'transform', 'rotate(-45deg)translateX(-14px)');
- img.setAttribute('src', createCursorImage(clr));
- img.style.width = '10px';
- cursor.appendChild(img);
-
- var name = document.createElement('div');
- name.style.backgroundColor = clr;
- name.style.color = lblClr;
- name.style.fontSize = '9pt';
- name.style.padding = '3px 7px';
- name.style.marginTop = '8px';
- name.style.borderRadius = '10px';
- name.style.maxWidth = '100px';
- name.style.overflow = 'hidden';
- name.style.textOverflow = 'ellipsis';
- name.style.whiteSpace = 'nowrap';
-
- mxUtils.write(name, username);
- cursor.appendChild(name);
- ui.diagramContainer.appendChild(cursor);
- selection = connectedSessions[sessionId].selection;
- }
- else
- {
- cursor = connectedSessions[sessionId].cursor;
- selection = connectedSessions[sessionId].selection;
- }
- };
- if (connectedSessions[sessionId] != null)
- {
- clearTimeout(connectedSessions[sessionId].inactiveTO);
- connectedSessions[sessionId].inactiveTO = setTimeout(function()
- {
- clientLeft(null, sessionId);
- }, INACTIVE_TIMEOUT);
- }
- var msgData = msg.data;
-
- switch (msg.type)
- {
- case 'cursor':
- createCursor();
- connectedSessions[sessionId].lastCursor = msgData;
- updateCursor(connectedSessions[sessionId], true);
- break;
- case 'diff':
- try
- {
- if (msgData.patch != null)
- {
- msg = sync.stringToObject(decodeURIComponent(msgData.patch));
- }
- else
- {
- msg = msgData.diff;
- }
- sync.receiveRemoteChanges(msg.d);
- }
- catch (e)
- {
- EditorUi.debug('P2PCollab: Diff msg error', e);
- }
- break;
- case 'selectionChange':
- if (urlParams['remote-selection'] != '0')
- {
- var pageId = (ui.currentPage != null) ?
- ui.currentPage.getId() : null;
-
- if (pageId == null ||
- (msgData.pageId != null &&
- msgData.pageId == pageId))
- {
- createCursor();
- for (var i = 0; i < msgData.removed.length; i++)
- {
- var id = msgData.removed[i];
- if (id != null)
- {
- var handler = selection[id];
- delete selection[id];
-
- if (handler != null)
- {
- handler.destroy();
- }
- }
- }
-
- for (var i = 0; i < msgData.added.length; i++)
- {
- var id = msgData.added[i];
- if (id != null)
- {
- var cell = graph.model.getCell(id);
- if (cell != null)
- {
- selection[id] = graph.highlightCell(cell,
- connectedSessions[sessionId].color, 60000,
- SELECTION_OPACITY, 3);
- }
- }
- }
- }
- }
- break;
- case 'notify':
- if (msgData.data != null)
- {
- msg = sync.stringToObject(decodeURIComponent(msgData.data));
- }
- else
- {
- msg = msgData.msg;
- }
- sync.handleMessageData(msg.d);
- break;
- }
- sync.file.fireEvent(new mxEventObject('realtimeMessage', 'message', msg));
- }
- catch (e)
- {
- if (window.console != null)
- {
- console.warn(e, msg, fromCId);
- }
- }
- };
-
- function createPeer(id, initiator)
- {
- if (NO_P2P || !SimplePeer.WEBRTC_SUPPORT)
- {
- return;
- }
-
- // TODO: Move URL to Editor.STUN_SERVER_URL, add to Editor.configure
- var p = new SimplePeer({
- initiator: initiator,
- config: { iceServers: [{ urls: 'stun:54.89.235.160:3478' }] }
- });
- p.on('signal', function(data)
- {
- sendReply('sendSignal', {to: id, from: myClientId, signal: data});
- });
- p.on('error', function(err)
- {
- delete newClients[id];
- EditorUi.debug('P2PCollab: p2p socket error', err);
- if (!destroyed && initiator && p.destroyed && connectedClient[id]) //If a client left, don't try to reconnect
- {
- EditorUi.debug('P2PCollab: p2p socket reconnecting', id);
- //Reconnect
- createPeer(id, true);
- }
- });
-
- p.on('connect', function()
- {
- delete newClients[id];
- if (p2pClients[id] == null || p2pClients[id].destroyed)
- {
- p2pClients[id] = p;
- connectedClient[id] = true;
- EditorUi.debug('P2PCollab: p2p socket connected', id);
- // if (mxUtils.isEmptyObject(newClients))
- // {
- //TODO Enable this when all messages can be routed via P2P
- //useSocket = false;
- //sendReply('movedToP2P', '');
- // }
- }
- else
- {
- p.noP2PMapDel = true;
- p.destroy();
- EditorUi.debug('P2PCollab: p2p socket duplicate', id);
- }
- });
-
- p.on('close', function()
- {
- if (!p.noP2PMapDel)
- {
- EditorUi.debug('P2PCollab: p2p socket closed', id);
- //Remove cursor and selection
- removeConnectedUserUi(clientsToSessions[id]);
- delete p2pClients[id];
- }
- });
-
- p.on('data', processMsg);
- newClients[id] = p;
-
- return p;
- };
-
- function clientsList(data)
- {
- myClientId = data.cId;
- fileJoined = true;
- for (var i = 0; i < data.list.length; i++)
- {
- createPeer(data.list[i], true);
- }
- };
-
- function signal(data)
- {
- if (NO_P2P) return;
- var p;
-
- if (newClients[data.from])
- {
- p = newClients[data.from];
- }
- else
- {
- p = createPeer(data.from, false);
- useSocket = true;
- }
-
- p.signal(data.signal);
- };
-
- function sendSignalFailed(data)
- {
- EditorUi.debug('P2PCollab: signal failed (socket not found on server)', data);
- delete newClients[data.to];
- connectedClient[data.to] = false; //TODO Should we call clientLeft?
- };
- function newClient(clientId)
- {
- useSocket = true;
- };
-
- function clientLeft(clientId, sessionId)
- {
- removeConnectedUserUi(sessionId || clientsToSessions[clientId]);
- if (clientId != null)
- {
- delete clientsToSessions[clientId];
- connectedClient[clientId] = false;
- }
- };
- this.joinFile = function(check)
- {
- if (destroyed) return;
- try
- {
- if (joinInProgress)
- {
- EditorUi.debug('P2PCollab: joinInProgress on', joinInProgress);
- lastError = 'busy';
- }
-
- joinInProgress = ++joinId;
-
- try
- {
- if (socket != null && socket.readyState == 1)
- {
- EditorUi.debug('P2PCollab: force closing socket on', socket.joinId)
- socket.close(1000);
- socket = null;
- }
- }
- catch(e)
- {
- EditorUi.debug('P2PCollab: closing socket error', e);
- } //Ignore
-
- var ws = new WebSocket(window.RT_WEBSOCKET_URL + '?id=' + channelId);
- if (socket == null)
- {
- socket = ws;
- }
-
- ws.addEventListener('open', function(event)
- {
- socket = ws;
- socket.joinId = joinInProgress;
- joinInProgress = false;
- sync.file.fireEvent(new mxEventObject('realtimeStateChanged'));
- EditorUi.debug('P2PCollab: open socket', socket.joinId);
- // Send join message
- if (!Editor.enableRealtimeCache)
- {
- window.setTimeout(function()
- {
- sync.sendJoinMessage();
- }, 0);
- }
- if (check)
- {
- sync.scheduleCleanup();
- }
- });
- function messageListener(event)
- {
- try
- {
- if (!NO_P2P)
- {
- EditorUi.debug('P2PCollab: msg received', [event]);
- }
- var data = JSON.parse(event.data);
-
- if (NO_P2P && data.action != 'message')
- {
- EditorUi.debug('P2PCollab: msg received', [event]);
- }
- switch (data.action)
- {
- case 'message':
- processMsg(data.msg, data.from);
- break;
- case 'clientsList':
- clientsList(data.msg);
- break;
- case 'signal':
- signal(data.msg);
- break;
- case 'newClient':
- newClient(data.msg);
- break;
- case 'clientLeft':
- clientLeft(data.msg);
- break;
- case 'sendSignalFailed':
- sendSignalFailed(data.msg);
- break;
- }
- }
- catch (e)
- {
- if (window.console != null)
- {
- console.warn(e, event);
- }
- }
- };
-
- ws.addEventListener('message', messageListener);
- var rejoinCalled = false;
-
- ws.addEventListener('close', mxUtils.bind(this, function(event)
- {
- EditorUi.debug('P2PCollab: WebSocket closed', ws.joinId, 'reconnecting', event.code, event.reason);
- EditorUi.debug('P2PCollab: closing socket on', ws.joinId);
- if (!destroyed && event.code != 1000 && joinId == ws.joinId) //Sometimes, a delayed even sometimes is received after another socket is established
- {
- if (joinInProgress == joinId)
- {
- EditorUi.debug('P2PCollab: joinInProgress in close on', ws.joinId);
- joinInProgress = false;
- }
-
- if (!rejoinCalled)
- {
- EditorUi.debug('P2PCollab: calling rejoin on', ws.joinId);
- window.setTimeout(mxUtils.bind(this, function()
- {
- rejoinCalled = true;
- this.joinFile(true);
- }), 500);
- }
- }
- sync.file.fireEvent(new mxEventObject('realtimeStateChanged'));
- }));
- ws.addEventListener('error', mxUtils.bind(this, function(event)
- {
- EditorUi.debug('P2PCollab: WebSocket error, reconnecting', event);
- EditorUi.debug('P2PCollab: error socket on', ws.joinId);
- if (!destroyed && joinId == ws.joinId) //Sometimes, a delayed even sometimes is received after another socket is established
- {
- if (joinInProgress == joinId)
- {
- EditorUi.debug('P2PCollab: joinInProgress in error on', ws.joinId);
- joinInProgress = false;
- }
-
- if (!rejoinCalled)
- {
- EditorUi.debug('P2PCollab: calling rejoin on', ws.joinId);
- rejoinCalled = true;
- this.joinFile(true);
- }
- }
- sync.file.fireEvent(new mxEventObject('realtimeStateChanged'));
- }));
- sync.file.fireEvent(new mxEventObject('realtimeStateChanged'));
- }
- catch (e)
- {
- lastError = e;
- sync.file.fireEvent(new mxEventObject('realtimeStateChanged'));
- }
- };
- function removeConnectedUserUi(sessionId)
- {
- var user = connectedSessions[sessionId];
- if (user != null)
- {
- var selection = user.selection;
- for (var id in selection)
- {
- if (selection[id] != null)
- {
- selection[id].destroy();
- }
- }
- if (user.cursor != null && user.cursor.parentNode != null)
- {
- user.cursor.parentNode.removeChild(user.cursor);
- }
- clearTimeout(user.inactiveTO);
- delete connectedSessions[sessionId];
- }
- };
- this.destroy = function()
- {
- if (destroyed) return;
- EditorUi.debug('P2PCollab: destroyed');
- destroyed = true;
- //Remove selection and cursor
- for (sessionId in connectedSessions)
- {
- removeConnectedUserUi(sessionId);
- }
- //Remove event listeners
- if (this.mouseListeners != null)
- {
- graph.removeMouseListener(this.mouseListeners);
- }
- if (this.selectionChangeListener != null)
- {
- graph.getSelectionModel().removeListener(this.selectionChangeListener);
- }
- if (this.shareCursorPositionListener != null)
- {
- ui.removeListener(this.shareCursorPositionListener);
- }
- if (this.cursorHandler != null)
- {
- mxEvent.removeListener(graph.container, 'scroll', this.cursorHandler);
- graph.view.removeListener(this.cursorHandler);
- ui.editor.removeListener(this.cursorHandler);
- ui.removeListener(this.cursorHandler);
- }
- // Close the socket
- if (socket != null && socket.readyState >= 1)
- {
- socket.close(1000);
- socket = null;
- }
- //Close P2P sockets
- for (var id in p2pClients)
- {
- if (p2pClients[id] != null)
- {
- p2pClients[id].destroy();
- }
- }
- sync.file.fireEvent(new mxEventObject('realtimeStateChanged'));
- };
- };
|