diff --git a/NetFluxWebsocketServer.js b/NetFluxWebsocketServer.js deleted file mode 100644 index 005cdcc6b..000000000 --- a/NetFluxWebsocketServer.js +++ /dev/null @@ -1,189 +0,0 @@ -;(function () { 'use strict'; -let Crypto = require('crypto'); -let WebSocket = require('ws'); -let LogStore = require('./storage/LogStore'); - -let LAG_MAX_BEFORE_DISCONNECT = 30000; -let LAG_MAX_BEFORE_PING = 15000; -let HISTORY_KEEPER_ID = "_HISTORY_KEEPER_"; - -let dropUser; - -let now = function () { return (new Date()).getTime(); }; - -let sendMsg = function (ctx, user, msg) { - try { - console.log('<' + JSON.stringify(msg)); - user.socket.send(JSON.stringify(msg)); - } catch (e) { - console.log(e.stack); - dropUser(ctx, user); - } -}; - -let sendChannelMessage = function (ctx, channel, msgStruct) { - msgStruct.unshift(0); - channel.forEach(function (user) { sendMsg(ctx, user, msgStruct); }); - if (msgStruct[2] === 'MSG') { - ctx.store.message(channel.id, JSON.stringify(msgStruct), function () { }); - } -}; - -dropUser = function (ctx, user) { - if (user.socket.readyState !== WebSocket.CLOSING - && user.socket.readyState !== WebSocket.CLOSED) - { - try { - user.socket.close(); - } catch (e) { - console.log("Failed to disconnect ["+user.id+"], attempting to terminate"); - try { - user.socket.terminate(); - } catch (ee) { - console.log("Failed to terminate ["+user.id+"] *shrug*"); - } - } - } - delete ctx.users[user.id]; - Object.keys(ctx.channels).forEach(function (chanName) { - let chan = ctx.channels[chanName]; - let idx = chan.indexOf(user); - if (idx < 0) { return; } - console.log("Removing ["+user.id+"] from channel ["+chanName+"]"); - chan.splice(idx, 1); - if (chan.length === 0) { - console.log("Removing empty channel ["+chanName+"]"); - delete ctx.channels[chanName]; - } else { - sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chanName, 'Quit: [ dropUser() ]']); - } - }); -}; - -let getHistory = function (ctx, channelName, handler) { - ctx.store.getMessages(channelName, function (msgStr) { handler(JSON.parse(msgStr)); }); -}; - -let randName = function () { return Crypto.randomBytes(16).toString('hex'); }; - -let handleMessage = function (ctx, user, msg) { - let json = JSON.parse(msg); - let seq = json.shift(); - let cmd = json[0]; - let obj = json[1]; - - user.timeOfLastMessage = now(); - user.pingOutstanding = false; - - if (cmd === 'JOIN') { - /*if (obj && obj.length !== 32) { - sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]); - return; - }*/ - let chanName = obj || randName(); - let chan = ctx.channels[chanName] = ctx.channels[chanName] || []; - chan.id = chanName; - sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'JOIN', chanName]); - chan.forEach(function (u) { sendMsg(ctx, user, [0, u.id, 'JOIN', chanName]); }); - chan.push(user); - sendChannelMessage(ctx, chan, [user.id, 'JOIN', chanName]); - return; - } - if (cmd === 'MSG') { - if (obj === HISTORY_KEEPER_ID) { - let parsed; - try { parsed = JSON.parse(json[2]); } catch (err) { return; } - if (parsed[0] === 'GET_HISTORY') { - console.log('getHistory ' + parsed[1]); - getHistory(ctx, parsed[1], function (msg) { - sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)]); - }); - sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, 0]); - } - return; - } - if (obj && !ctx.channels[obj] && !ctx.users[obj]) { - sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]); - return; - } - let target; - json.unshift(user.id); - if ((target = ctx.channels[obj])) { - sendChannelMessage(ctx, target, json); - return; - } - if ((target = ctx.users[obj])) { - json.unshift(0); - sendMsg(ctx, target, json); - return; - } - } - if (cmd === 'LEAVE') { - let err; - let chan; - let idx; - if (!obj) { err = 'EINVAL'; } - if (!err && !(chan = ctx.channels[obj])) { err = 'ENOENT'; } - if (!err && (idx = chan.indexOf(user)) === -1) { err = 'NOT_IN_CHAN'; } - if (err) { - sendMsg(ctx, user, [seq, 'ERROR', err]); - return; - } - json.unshift(user.id); - sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chan.id]); - chan.splice(idx, 1); - } - if (cmd === 'PING') { - sendMsg(ctx, user, [seq, 'PONG', obj]); - return; - } -}; - -let run = module.exports.run = function (storage, socketServer) { - let ctx = { - users: {}, - channels: {}, - store: LogStore.create('messages.log', storage) - }; - setInterval(function () { - Object.keys(ctx.users).forEach(function (userId) { - let u = ctx.users[userId]; - if (now() - u.timeOfLastMessage > LAG_MAX_BEFORE_DISCONNECT) { - dropUser(ctx, u); - } else if (!u.pingOutstanding && now() - u.timeOfLastMessage > LAG_MAX_BEFORE_PING) { - sendMsg(ctx, u, [0, 'PING', now()]); - u.pingOutstanding = true; - } - }); - }, 5000); - socketServer.on('connection', function(socket) { - if(socket.upgradeReq.url !== '/cryptpad_websocket') { return; } - let conn = socket.upgradeReq.connection; - let user = { - addr: conn.remoteAddress + '|' + conn.remotePort, - socket: socket, - id: randName(), - timeOfLastMessage: now(), - pingOutstanding: false - }; - ctx.users[user.id] = user; - sendMsg(ctx, user, [0, 'IDENT', user.id]); - socket.on('message', function(message) { - console.log('>'+message); - try { - handleMessage(ctx, user, message); - } catch (e) { - console.log(e.stack); - dropUser(ctx, user); - } - }); - socket.on('close', function (evt) { - for (let userId in ctx.users) { - if (ctx.users[userId].socket === socket) { - dropUser(ctx, ctx.users[userId]); - } - } - }); - }); -}; -}()); diff --git a/www/common/RealtimeTextSocket.js b/www/common/RealtimeTextSocket.js index 944d7f84a..28012cce8 100644 --- a/www/common/RealtimeTextSocket.js +++ b/www/common/RealtimeTextSocket.js @@ -222,8 +222,8 @@ define([ verbose(message); allMessages.push(message); if (!initializing) { - if (toReturn.onLocal) { - toReturn.onLocal(); + if (config.onLocal) { + config.onLocal(); } } realtime.message(message); diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index ed665e9e3..ed982580e 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -18,11 +18,10 @@ define([ '/common/messages.js', '/common/netflux-client.js', '/common/crypto.js', - '/common/TextPatcher.js', '/common/es6-promise.min.js', '/common/chainpad.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Messages, Netflux, Crypto, TextPatcher) { +], function (Messages, Netflux, Crypto) { var $ = window.jQuery; var ChainPad = window.ChainPad; var PARANOIA = true; @@ -117,6 +116,7 @@ define([ initializing = false; // execute an onReady callback if one was supplied + // FIXME this should be once the chain has synced if (config.onReady) { config.onReady({ realtime: realtime @@ -227,13 +227,6 @@ define([ // Open a Chainpad session realtime = createRealtime(); - toReturn.onEvent = function (newText) { - // assert to show that we're not out of sync - if (realtime.getUserDoc() !== newText) { - warn("realtime.getUserDoc() !== newText"); - } - }; - // Sending a message... realtime.onMessage(function(message) { // Filter messages sent by Chainpad to make it compatible with Netflux @@ -265,12 +258,6 @@ define([ wc.history_keeper = hc; if (hc) { network.sendto(hc, JSON.stringify(['GET_HISTORY', wc.id])); } - - toReturn.patchText = TextPatcher.create({ - realtime: realtime, - logging: true - }); - realtime.start(); }; @@ -290,6 +277,16 @@ define([ Netflux.connect(websocketUrl).then(function(network) { // pass messages that come out of netflux into our local handler + network.on('disconnect', function (evt) { + // TODO also abort if Netflux times out + // that will be managed in Netflux-client.js + if (config.onAbort) { + config.onAbort({ + reason: evt.reason + }); + } + }); + network.on('message', function (msg, sender) { // Direct message var wchan = findChannelById(network.webChannels, channel); if(wchan) { diff --git a/www/p/main.js b/www/p/main.js index 89b6bef8c..1c9fd19f9 100644 --- a/www/p/main.js +++ b/www/p/main.js @@ -311,25 +311,14 @@ define([ toolbar.failed(); }; - var rti = module.realtimeInput = realtimeInput.start(realtimeOptions); - - - /* It's incredibly important that you assign 'rti.onLocal' - It's used inside of realtimeInput to make sure that all changes - make it into chainpad. - - It's being assigned this way because it can't be passed in, and - and can't be easily returned from realtime input without making - the code less extensible. - */ - var propogate = rti.onLocal = function () { + var onLocal = realtimeOptions.onLocal = function () { + if (initializing) { return; } var shjson = stringifyDOM(inner); - if (!rti.patchText(shjson)) { - return; - } - rti.onEvent(shjson); + rti.patchText(shjson); }; + var rti = module.realtimeInput = realtimeInput.start(realtimeOptions); + /* hitting enter makes a new line, but places the cursor inside of the
instead of the

. This makes it such that you cannot type until you click, which is rather unnacceptable. @@ -342,12 +331,13 @@ define([ var easyTest = window.easyTest = function () { cursor.update(); var start = cursor.Range.start; - var test = TypingTest.testInput(inner, start.el, start.offset, propogate); - propogate(); + var test = TypingTest.testInput(inner, start.el, start.offset, onLocal); + // why twice? + onLocale(); return test; }; - editor.on('change', propogate); + editor.on('change', onLocal); }); }; diff --git a/www/pad/main.js b/www/pad/main.js index 9429e552c..901fce1c5 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -11,15 +11,18 @@ define([ '/common/json-ot.js', '/common/TypingTests.js', 'json.sortify', + '/common/TextPatcher.js', '/bower_components/diff-dom/diffDOM.js', '/bower_components/jquery/dist/jquery.min.js', '/customize/pad.js' -], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, Toolbar, Cursor, JsonOT, TypingTest, JSONSortify) { +], function (Config, Messages, Crypto, realtimeInput, Hyperjson, Hyperscript, + Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher) { + var $ = window.jQuery; var ifrw = $('#pad-iframe')[0].contentWindow; var Ckeditor; // to be initialized later... var DiffDom = window.diffDOM; - + var stringify = function (obj) { return JSONSortify(obj); }; @@ -288,7 +291,7 @@ define([ var shjson2 = stringifyDOM(inner); if (shjson2 !== shjson) { console.error("shjson2 !== shjson"); - module.realtimeInput.patchText(shjson2); + module.patchText(shjson2); } }; @@ -304,12 +307,21 @@ define([ /* TODO handle disconnects and such*/ }; + // this should only ever get called once, when the chain syncs var onReady = realtimeOptions.onReady = function (info) { - console.log("Unlocking editor"); - initializing = false; - setEditable(true); + module.patchText = TextPatcher.create({ + realtime: info.realtime, + logging: false, + }); + + module.realtime = info.realtime; + var shjson = info.realtime.getUserDoc(); applyHjson(shjson); + + console.log("Unlocking editor"); + setEditable(true); + initializing = false; }; var onAbort = realtimeOptions.onAbort = function (info) { @@ -320,21 +332,9 @@ define([ toolbar.failed(); }; + var onLocal = realtimeOptions.onLocal = function () { + if (initializing) { return; } - - - - var rti = module.realtimeInput = realtimeInput.start(realtimeOptions); - - /* It's incredibly important that you assign 'rti.onLocal' - It's used inside of realtimeInput to make sure that all changes - make it into chainpad. - - It's being assigned this way because it can't be passed in, and - and can't be easily returned from realtime input without making - the code less extensible. - */ - var propogate = rti.onLocal = function () { // serialize your DOM into an object var hjson = Hyperjson.fromDOM(inner, isNotMagicLine, brFilter); @@ -344,12 +344,15 @@ define([ } // stringify the json and send it into chainpad var shjson = stringify(hjson); - if (!rti.patchText(shjson)) { - return; + module.patchText(shjson); + + if (module.realtime.getUserDoc() !== shjson) { + console.error("realtime.getUserDoc() !== shjson"); } - rti.onEvent(shjson); }; + var rti = module.realtimeInput = realtimeInput.start(realtimeOptions); + /* hitting enter makes a new line, but places the cursor inside of the
instead of the

. This makes it such that you cannot type until you click, which is rather unnacceptable. @@ -359,7 +362,7 @@ define([ the first such keypress will not be inserted into the P. */ inner.addEventListener('keydown', cursor.brFix); - editor.on('change', propogate); + editor.on('change', onLocal); // export the typing tests to the window. // call like `test = easyTest()` @@ -367,8 +370,8 @@ define([ var easyTest = window.easyTest = function () { cursor.update(); var start = cursor.Range.start; - var test = TypingTest.testInput(inner, start.el, start.offset, propogate); - propogate(); + var test = TypingTest.testInput(inner, start.el, start.offset, onLocal); + onLocal(); return test; }; });