From b79ba61984a68c67f80713c9ffbc4806578f450f Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 15 Jan 2019 10:46:39 +0100 Subject: [PATCH] Improve realtime channel for onlyoffice --- www/common/onlyoffice/inner.js | 5 +- www/common/onlyoffice/main.js | 24 +++-- www/common/outer/onlyoffice.js | 140 +++++++++++++++++++----------- www/common/sframe-common-outer.js | 8 +- 4 files changed, 116 insertions(+), 61 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index bb6184cf4..0281b37c5 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -88,7 +88,10 @@ define([ var openRtChannel = function (data) { // XXX var channel = Hash.createChannelId(); - ctx.sframeChan.query('Q_OO_OPENCHANNEL', channel, function (err, obj) { + ctx.sframeChan.query('Q_OO_OPENCHANNEL', { + channel: channel, + lastCp: data.lastCp + }, function (err, obj) { if (err || (obj && obj.error)) { console.error(err || (obj && obj.error)); } }); }; diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index ce56949f9..11f797936 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -40,6 +40,8 @@ define([ obj.ooType = window.location.pathname.replace(/^\//, '').replace(/\/$/, ''); }; var addRpc = function (sframeChan, Cryptpad, Utils) { + var crypto = Utils.Crypto.createEncryptor(Utils.secret.keys); + sframeChan.on('Q_OO_SAVE', function (data, cb) { var chanId = Utils.Hash.hrefToHexChannelId(data.url); Cryptpad.getPadAttribute('lastVersion', function (err, data) { @@ -57,16 +59,26 @@ define([ Cryptpad.onlyoffice.execCommand({ cmd: 'OPEN_CHANNEL', data: { - channel: data, - secret: secret + // XXX add owners? + // owners: something... + channel: data.channel, + lastCp: data.lastCp, + padChan: Utils.secret.channel, + validateKey: Utils.secret.keys.validateKey } }, cb); }); - sframeChan.on('Q_OO_COMMAND', function (data, cb) { - Cryptpad.onlyoffice.execCommand(data, cb); + sframeChan.on('Q_OO_COMMAND', function (obj, cb) { + if (obj.cmd === 'SEND_MESSAGE' && !obj.data.isCp) { + obj.data.msg = crypto.encrypt(obj.data.msg); + } + Cryptpad.onlyoffice.execCommand(obj, cb); }); - Cryptpad.onlyoffice.onEvent.reg(function (data) { - sframeChan.event('EV_OO_EVENT', data); + Cryptpad.onlyoffice.onEvent.reg(function (obj) { + if (obj.ev === 'MESSAGE') { + obj.data = crypto.decrypt(obj.data, Utils.secret.keys.validateKeys); + } + sframeChan.event('EV_OO_EVENT', obj); }); }; SFCommonO.start({ diff --git a/www/common/outer/onlyoffice.js b/www/common/outer/onlyoffice.js index 8d0ce81fd..ea98d4033 100644 --- a/www/common/outer/onlyoffice.js +++ b/www/common/outer/onlyoffice.js @@ -1,28 +1,14 @@ define([ '/common/common-util.js', - '/common/common-constants.js', - '/customize/messages.js', - '/bower_components/chainpad-crypto/crypto.js', -], function (Util, Constants, Messages, Crypto) { +], function (Util) { var OO = {}; - var convertToUint8 = function (obj) { - var l = Object.keys(obj).length; - var u = new Uint8Array(l); - for (var i = 0; i Set the ID to our client object + // ==> Use our netflux ID to create our client ID if (!c.id) { c.id = chan.wc.myID + '-' + client; } - // ==> Send the join message to the other members of the channel - // XXX bcast a "join" message to the channel? + /// XXX send chan.history to client // ==> And push the new tab to the list chan.clients.push(client); @@ -55,11 +40,14 @@ define([ ctx.channels[channel] = ctx.channels[channel] || {}; - var chan = ctx.channels[channel]; + chan = ctx.channels[channel]; + + // Create our client ID using the netflux ID if (!c.id) { c.id = wc.myID + '-' + client; } + + // If this is a reconnect, we have a new netflux ID so we're going to fix + // all our client IDs. if (chan.clients) { - // If 2 tabs from the same worker have been opened at the same time, - // we have to fix both of them chan.clients.forEach(function (cl) { if (ctx.clients[cl] && !ctx.clients[cl].id) { ctx.clients[cl].id = wc.myID + '-' + cl; @@ -67,9 +55,6 @@ define([ }); } - - if (!chan.encryptor) { chan.encryptor = Crypto.createEncryptor(secret.keys); } - wc.on('join', function () { // XXX }); @@ -77,12 +62,7 @@ define([ // XXX }); wc.on('message', function (cryptMsg) { - var msg = chan.encryptor.decrypt(cryptMsg, secret.keys && secret.keys.validateKey); - var parsed; - try { - parsed = JSON.parse(msg); - // XXX - } catch (e) { console.error(e); } + ctx.emit('MESSAGE', cryptMsg, chan.clients); }); chan.wc = wc; @@ -96,24 +76,94 @@ define([ }); }; - if (!first) { return; } - chan.clients = [client]; - first = false; - cb(); + if (first) { + chan.clients = [client]; + chan.lastCp = obj.lastCp; + first = false; + cb(); + } + + var hk = network.historyKeeper; + var cfg = { + validateKey: obj.validateKey, + lastKnownHash: chan.lastKnownHash, + owners: obj.owners, + }; + var msg = ['GET_HISTORY', wc.id, cfg]; + // Add the validateKey if we are the channel creator and we have a validateKey + if (hk) { + network.sendto(hk, JSON.stringify(msg)).then(function () { + }, function (err) { + console.error(err); + }); + } + }; + network.on('message', function (msg, sender) { + var hk = network.historyKeeper; + if (sender !== hk) { return; } + + // Parse the message + var parsed; + try { + parsed = JSON.parse(msg); + } catch (e) {} + if (!parsed) { return; } + + + // Keep only metadata messages for the current channel + if (parsed.channel && parsed.channel !== channel) { return; } + // Ignore the metadata message + if (parsed.validateKey && parsed.channel) { return; } + // End of history: emit READY + if (parsed.state && parsed.state === 1 && parsed.channel) { + ctx.emit('READY', ''); + return; + } + if (parsed.error && parsed.channel) { return; } + + var msg = parsed[4]; + + // Keep only the history for our channel + if (msg[3] !== channel) { return; } + + if (chan.lastCp) { + if (chan.lastCp === msg) { + delete chan.lastCp; + } + return; + } + + var isCp = /^cp\|/.test(msg); + if (isCp) { return; } + + chan.lastKnownHash = msg.slice(0,64); + ctx.emit('MESSAGE', msg, chan.clients); + }); + network.join(channel).then(onOpen, function (err) { return void cb({error: err}); }); network.on('reconnect', function () { - if (!ctx.channels[channel]) { console.log("cant reconnect", channel); return; } + if (!ctx.channels[channel]) { return; } network.join(channel).then(onOpen, function (err) { console.error(err); }); }); }; + var sendMessage = function (ctx, data, clientId, cb) { + var c = ctx.clients[clientId]; + if (!c) { return void cb({ error: 'NOT_IN_CHANNEL' }); } + var chan = ctx.channels[c.channel]; + if (!chan) { return void cb({ error: 'INVALID_CHANNEL' }); } + if (data.isCp) { + return void chan.sendMsg(data.isCp, cb); + } + chan.sendMsg(data.msg, cb); + }; var leaveChannel = function (ctx, padChan) { // Leave channel and prevent reconnect when we leave a pad @@ -143,19 +193,6 @@ define([ } } - // Send the leave message to the channel we were in - if (ctx.clients[clientId]) { - var leaveMsg = { - leave: true, - id: ctx.clients[clientId].id - }; - chan = ctx.channels[ctx.clients[clientId].channel]; - if (chan) { - chan.sendMsg(JSON.stringify(leaveMsg)); - ctx.emit('MESSAGE', leaveMsg, chan.clients); - } - } - delete ctx.clients[clientId]; }; @@ -179,6 +216,9 @@ define([ oo.execCommand = function (clientId, obj, cb) { var cmd = obj.cmd; var data = obj.data; + if (cmd === 'SEND_MESSAGE') { + return void sendMessage(ctx, data, clientId, cb); + } if (cmd === 'OPEN_CHANNEL') { return void openChannel(ctx, data, clientId, cb); } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 4ccd28b65..118fe5652 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -53,7 +53,7 @@ define([ _Constants, _Feedback, _LocalStore, _AppConfig, _Test) { CpNfOuter = _CpNfOuter; Cryptpad = _Cryptpad; - Crypto = _Crypto; + Crypto = Utils.Crypto = _Crypto; Cryptget = _Cryptget; SFrameChannel = _SFrameChannel; FilePicker = _FilePicker; @@ -156,7 +156,7 @@ define([ var w = waitFor(); // No password for drive, profile and todo cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) { - secret = s; + secret = Utils.secret = s; Cryptpad.getShareHashes(secret, function (err, h) { hashes = h; w(); @@ -165,7 +165,7 @@ define([ } else { var parsed = Utils.Hash.parsePadUrl(window.location.href); var todo = function () { - secret = Utils.Hash.getSecrets(parsed.type, void 0, password); + secret = Utils.secret = Utils.Hash.getSecrets(parsed.type, void 0, password); Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; })); }; @@ -951,7 +951,7 @@ define([ // Create a new hash password = data.password; var newHash = Utils.Hash.createRandomHash(parsed.type, password); - secret = Utils.Hash.getSecrets(parsed.type, newHash, password); + secret = Utils.secret = Utils.Hash.getSecrets(parsed.type, newHash, password); // Update the hash in the address bar var ohc = window.onhashchange;