From efdecd4059d4e3fc0557fd7c26f6f95313d6e553 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 14 Jan 2019 14:01:22 +0100 Subject: [PATCH] First step for realtime in onlyoffice apps --- www/common/cryptpad-common.js | 9 + www/common/onlyoffice/inner.js | 34 +++- www/common/onlyoffice/main.js | 15 ++ .../onlyoffice/sdkjs/cell/sdk-all-min.js | 66 ++++-- www/common/outer/async-store.js | 25 ++- www/common/outer/onlyoffice.js | 191 ++++++++++++++++++ www/common/outer/store-rpc.js | 2 + 7 files changed, 319 insertions(+), 23 deletions(-) create mode 100644 www/common/outer/onlyoffice.js diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 0e2b7c62a..296bf4a1b 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -623,6 +623,13 @@ define([ }); }; + // Onlyoffice + var onlyoffice = common.onlyoffice = {}; + onlyoffice.execCommand = function (data, cb) { + postMessage("OO_COMMAND", data, cb); + }; + onlyoffice.onEvent = Util.mkEvent(); + // Messenger var messenger = common.messenger = {}; messenger.execCommand = function (data, cb) { @@ -1061,6 +1068,8 @@ define([ common.onNetworkReconnect.fire(data); }); }, + // OnlyOffice + OO_EVENT: common.onlyoffice.onEvent.fire, // Chat CHAT_EVENT: common.messenger.onEvent.fire, // Cursor diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 54279ad8c..bb6184cf4 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -15,6 +15,7 @@ define([ '/common/onlyoffice/oocell_base.js', '/common/onlyoffice/oodoc_base.js', '/common/onlyoffice/ooslide_base.js', + '/common/outer/worker-channel.js', '/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/file-saver/FileSaver.min.js', @@ -38,7 +39,8 @@ define([ FileCrypto, EmptyCell, EmptyDoc, - EmptySlide) + EmptySlide, + Channel) { var saveAs = window.saveAs; var Nacl = window.nacl; @@ -83,6 +85,35 @@ define([ return file; }; + var openRtChannel = function (data) { + // XXX + var channel = Hash.createChannelId(); + ctx.sframeChan.query('Q_OO_OPENCHANNEL', channel, function (err, obj) { + if (err || (obj && obj.error)) { console.error(err || (obj && obj.error)); } + }); + }; + + var mkChannel = function () { + var msgEv = Util.mkEvent(); + var iframe = $('#cp-app-oo-container > iframe')[0].contentWindow; + window.addEventListener('message', function (msg) { + if (msg.source !== iframe) { return; } + msgEv.fire(msg); + }); + var postMsg = function (data) { + iframe.postMessage(data, '*'); + }; + Channel.create(msgEv, postMsg, function (chan) { + APP.chan = chan; + chan.on('CMDFROMOO', function (data) { + console.log('command from oo', data); + setTimeout(function () { + chan.event('RTMSG', 'Pewpewpew'); + }, 2000); + }); + }); + }; + var startOO = function (blob, file) { if (APP.ooconfig) { return void console.error('already started'); } var url = URL.createObjectURL(blob); @@ -144,6 +175,7 @@ define([ if (ifr) { ifr.remove(); } }; APP.docEditor = new DocsAPI.DocEditor("cp-app-oo-placeholder", APP.ooconfig); + mkChannel(); }; var getContent = APP.getContent = function () { diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index b400b1df8..ce56949f9 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -53,6 +53,21 @@ define([ Cryptpad.setPadAttribute('lastVersion', data.url, cb); }); }); + sframeChan.on('Q_OO_OPENCHANNEL', function (data, cb) { + Cryptpad.onlyoffice.execCommand({ + cmd: 'OPEN_CHANNEL', + data: { + channel: data, + secret: secret + } + }, cb); + }); + sframeChan.on('Q_OO_COMMAND', function (data, cb) { + Cryptpad.onlyoffice.execCommand(data, cb); + }); + Cryptpad.onlyoffice.onEvent.reg(function (data) { + sframeChan.event('EV_OO_EVENT', data); + }); }; SFCommonO.start({ type: 'oo', diff --git a/www/common/onlyoffice/sdkjs/cell/sdk-all-min.js b/www/common/onlyoffice/sdkjs/cell/sdk-all-min.js index 74f03ebe8..200fa7fa6 100644 --- a/www/common/onlyoffice/sdkjs/cell/sdk-all-min.js +++ b/www/common/onlyoffice/sdkjs/cell/sdk-all-min.js @@ -3770,6 +3770,50 @@ AscBrowser.convertToRetinaValue = function(value, isScale) sockjs = this.sockjs = {}; //t._state = true; + var send = function (data) { + setTimeout(function () { + console.log(data); + sockjs.onmessage({ + data: JSON.stringify(data) + }); + }); + }; + var license = { + type: 'license', + license: { + type: 3, + light: false, + trial: false, + rights: 1, + buildVersion: "4.3.3", + buildNumber: 4, + branding: false + } + }; + var channel; + +require([ + '/common/outer/worker-channel.js', + '/common/common-util.js' +], function (Channel, Util) { + var msgEv = Util.mkEvent(); + var p = window.parent; + window.addEventListener('message', function (msg) { + if (msg.source !== p) { return; } + msgEv.fire(msg); + }); + var postMsg = function (data) { + p.postMessage(data, '*'); + }; + Channel.create(msgEv, postMsg, function (chan) { + channel = chan; + send(license); + chan.on('RTMSG', function (data) { + console.log('receiving RTMSG', data); + }); + }); +}); + sockjs.onopen = function() { /* if (t.reconnectTimeout) { @@ -3861,14 +3905,6 @@ AscBrowser.convertToRetinaValue = function(value, isScale) console.error('Close realtime'); }; - var send = function (data) { - setTimeout(function () { - console.log(data); - sockjs.onmessage({ - data: JSON.stringify(data) - }); - }); - }; sockjs.send = function (data) { console.log(data); try { @@ -3893,6 +3929,7 @@ AscBrowser.convertToRetinaValue = function(value, isScale) }; send(msg); send(msg2); + channel.event('CMDFROMOO', 'Hey'); break; case 'getMessages': msg = {}; @@ -3900,19 +3937,6 @@ AscBrowser.convertToRetinaValue = function(value, isScale) } }; - var license = { - type: 'license', - license: { - type: 3, - light: false, - trial: false, - rights: 1, - buildVersion: "4.3.3", - buildNumber: 4, - branding: false - } - }; - send(license); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 32b02eb23..50a136b81 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -11,6 +11,7 @@ define([ '/common/common-messaging.js', '/common/common-messenger.js', '/common/outer/cursor.js', + '/common/outer/onlyoffice.js', '/common/outer/chainpad-netflux-worker.js', '/common/outer/network-config.js', '/customize/application_config.js', @@ -21,7 +22,7 @@ define([ '/bower_components/nthen/index.js', '/bower_components/saferphore/index.js', ], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger, - Cursor, CpNfWorker, NetConfig, AppConfig, + Cursor, OnlyOffice, CpNfWorker, NetConfig, AppConfig, Crypto, ChainPad, Listmap, nThen, Saferphore) { var Store = {}; @@ -927,6 +928,14 @@ define([ } }; + // OnlyOffice + Store.onlyoffice = { + execCommand: function (clientId, data, cb) { + if (!store.onlyoffice) { return void cb({error: 'OnlyOffice is disabled'}); } + store.onlyoffice.execCommand(data, cb); + } + }; + // Cursor Store.cursor = { @@ -1237,6 +1246,7 @@ define([ var dropChannel = function (chanId) { store.messenger.leavePad(chanId); store.cursor.leavePad(chanId); + store.onlyoffice.leavePad(chanId); if (!Store.channels[chanId]) { return; } @@ -1256,6 +1266,7 @@ define([ messengerEventClients.splice(messengerIdx, 1); } store.cursor.removeClient(clientId); + store.onlyoffice.removeClient(clientId); Object.keys(Store.channels).forEach(function (chanId) { var chanIdx = Store.channels[chanId].clients.indexOf(clientId); if (chanIdx !== -1) { @@ -1339,6 +1350,17 @@ define([ }); }; + var loadOnlyOffice = function () { + store.onlyoffice = OnlyOffice.init(store, function (ev, data, clients) { + clients.forEach(function (cId) { + postMessage(cId, 'OO_EVENT', { + ev: ev, + data: data + }); + }); + }); + }; + ////////////////////////////////////////////////////////////////// /////////////////////// Init ///////////////////////////////////// ////////////////////////////////////////////////////////////////// @@ -1426,6 +1448,7 @@ define([ loadSharedFolders(waitFor); loadMessenger(); loadCursor(); + loadOnlyOffice(); }).nThen(function () { var requestLogin = function () { broadcast([], "REQUEST_LOGIN"); diff --git a/www/common/outer/onlyoffice.js b/www/common/outer/onlyoffice.js new file mode 100644 index 000000000..8d0ce81fd --- /dev/null +++ b/www/common/outer/onlyoffice.js @@ -0,0 +1,191 @@ +define([ + '/common/common-util.js', + '/common/common-constants.js', + '/customize/messages.js', + '/bower_components/chainpad-crypto/crypto.js', +], function (Util, Constants, Messages, Crypto) { + 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 + 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? + + // ==> And push the new tab to the list + chan.clients.push(client); + return void cb(); + } + + var onOpen = function (wc) { + + ctx.channels[channel] = ctx.channels[channel] || {}; + + var chan = ctx.channels[channel]; + if (!c.id) { c.id = wc.myID + '-' + client; } + 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; + } + }); + } + + + if (!chan.encryptor) { chan.encryptor = Crypto.createEncryptor(secret.keys); } + + wc.on('join', function () { + // XXX + }); + wc.on('leave', function (peer) { + // 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); } + }); + + chan.wc = wc; + chan.sendMsg = function (msg, cb) { + cb = cb || function () {}; + var cmsg = chan.encryptor.encrypt(msg); + wc.bcast(cmsg).then(function () { + cb(); + }, function (err) { + cb({error: err}); + }); + }; + + if (!first) { return; } + chan.clients = [client]; + first = false; + cb(); + }; + + 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; } + network.join(channel).then(onOpen, function (err) { + console.error(err); + }); + }); + }; + + + var leaveChannel = function (ctx, padChan) { + // Leave channel and prevent reconnect when we leave a pad + Object.keys(ctx.channels).some(function (ooChan) { + var channel = ctx.channels[ooChan]; + if (channel.padChan !== padChan) { return; } + if (channel.wc) { channel.wc.leave(); } + delete ctx.channels[ooChan]; + return true; + }); + }; + + // Remove the client from all its channels when a tab is closed + var removeClient = function (ctx, clientId) { + var filter = function (c) { + return c !== clientId; + }; + + // Remove the client from our channels + var chan; + for (var k in ctx.channels) { + chan = ctx.channels[k]; + chan.clients = chan.clients.filter(filter); + if (chan.clients.length === 0) { + if (chan.wc) { chan.wc.leave(); } + delete ctx.channels[k]; + } + } + + // 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]; + }; + + + + OO.init = function (store, emit) { + var oo = {}; + var ctx = { + store: store, + emit: emit, + channels: {}, + clients: {} + }; + + oo.removeClient = function (clientId) { + removeClient(ctx, clientId); + }; + oo.leavePad = function (padChan) { + leaveChannel(ctx, padChan); + }; + oo.execCommand = function (clientId, obj, cb) { + var cmd = obj.cmd; + var data = obj.data; + if (cmd === 'OPEN_CHANNEL') { + return void openChannel(ctx, data, clientId, cb); + } + }; + + return cursor; + }; + + return OO; +}); diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index f6ed7d503..6f2682d63 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -62,6 +62,8 @@ define([ ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers, // Chat CHAT_COMMAND: Store.messenger.execCommand, + // OnlyOffice + OO_COMMAND: Store.onlyoffice.execCommand, // Cursor CURSOR_COMMAND: Store.cursor.execCommand, // Pad