function P2PCollab(ui) { socket = io(App.SOCKET_IO_SRV); var svgP1 = ''; var graph = ui.editor.graph; var userCount = 0; var userColors = [ '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#000000' ]; var connectedUsers = {}, messageId = 1, clientLastMsgId = {}; var myClientId, newClients = {}, p2pClients = {}, useSocket = true, fileJoined = false; function sendMessage(type, data) { var user = ui.getCurrentUser(); if (!fileJoined || user == null || user.email == null) return; var msg = JSON.stringify({from: myClientId, id: messageId, type: type, userId: user.id, username: user.displayName, data: data}); messageId++; if (useSocket) { socket.emit('message', msg); } for (p2pId in p2pClients) { p2pClients[p2pId].send(msg); } }; this.sendMessage = sendMessage; graph.addMouseListener( { startX: 0, startY: 0, scrollLeft: 0, scrollTop: 0, mouseDown: function(sender, me) {}, mouseMove: function(sender, me) //TODO debounce this function { var tr = graph.view.translate; var s = graph.view.scale; sendMessage('cursor', {x: me.graphX / s - tr.x, y: me.graphY / s - tr.y}); }, mouseUp: function(sender, me) {} }); function processMsg(msg) { msg = JSON.parse(msg); //Safeguard from duplicate messages if (clientLastMsgId[msg.from] >= msg.id) return; clientLastMsgId[msg.from] = msg.id; var username = msg.username? msg.username : 'Anonymous'; var userId = msg.userId; var cursor; if (connectedUsers[userId] == null) { var clr = userColors[userCount]; connectedUsers[userId] = { cursor: document.createElement('div'), index: userCount, color: clr }; userCount++; cursor = connectedUsers[userId].cursor; cursor.style.position = 'absolute'; cursor.style.zIndex = 5000; var svg = 'data:image/svg+xml;base64,' + btoa(svgP1 + clr + '">' + svgP2); cursor.innerHTML = '
' + username + '
'; document.body.appendChild(cursor); } else { cursor = connectedUsers[userId].cursor; } var msgData = msg.data; switch (msg.type) { case 'cursor': var tr = graph.view.translate; var s = graph.view.scale; var container = ui.diagramContainer; var offset = mxUtils.getOffset(container); msgData.x = (tr.x + msgData.x) * s - container.scrollLeft + offset.x; msgData.y = (tr.y + msgData.y) * s - container.scrollTop + offset.y; cursor.style.left = msgData.x + 'px'; cursor.style.top = msgData.y + 'px'; break; case 'diff': var file = ui.getCurrentFile(); if (file.sync != null) { file.sync.p2pCatchup(msgData.data, msgData.from, msgData.to, msgData.id, file.getDescriptor(), function() { console.log('Diff Synced'); }, function() { console.log('Diff Error'); }); } break; } } socket.on('message', processMsg); function createPeer(id, initiator) { if (!SimplePeer.WEBRTC_SUPPORT) { return; } var p = new SimplePeer({ initiator: initiator }); p.on('signal', function(data) { socket.emit('sendSignal', {to: id, from: myClientId, signal: data}); }); p.on('error', function(err) { delete newClients[id]; console.log('error', err); //TODO Handle errors }); p.on('connect', function() { p2pClients[id] = p; delete newClients[id]; if (Object.keys(newClients).length == 0) { useSocket = false; socket.emit('movedToP2P', ''); } }); p.on('close', function() { delete p2pClients[id]; }); p.on('data', processMsg); newClients[id] = p; return p; }; socket.on('clientsList', function(data) { myClientId = data.cId; for (var i = 0; i < data.list.length; i++) { createPeer(data.list[i], true); } }); socket.on('signal', function(data) { var p; if (newClients[data.from]) { p = newClients[data.from]; } else { p = createPeer(data.from, false); useSocket = true; } p.signal(data.signal); }); socket.on('newClient', function(clientId) { useSocket = true; }); this.joinFile = function(channelId) { socket.emit('join', {name: channelId}); fileJoined = true; }; };