From ec2f21ec643e40ea0605a2d91f681532071a684c Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 24 Jan 2019 15:26:35 +0100 Subject: [PATCH] Implement lock mechanisms for spreadsheets --- .jshintignore | 3 +- www/common/onlyoffice/inner.js | 355 +++++++++++++++--- .../onlyoffice/sdkjs/cell/sdk-all-min.js | 138 +++---- .../onlyoffice/sdkjs/slide/sdk-all-min.js | 169 ++++----- .../onlyoffice/sdkjs/word/sdk-all-min.js | 169 ++++----- 5 files changed, 524 insertions(+), 310 deletions(-) diff --git a/.jshintignore b/.jshintignore index 82e1d9901..e3735dc34 100644 --- a/.jshintignore +++ b/.jshintignore @@ -4,7 +4,8 @@ www/common/pdfjs/ www/common/tippy/ www/common/highlight/ www/common/jquery-ui/ -www/common/onlyoffice/* +www/common/onlyoffice/sdkjs +www/common/onlyoffice/web-apps server.js www/common/old-media-tag.js diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 89f046c69..5f27053c1 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -68,7 +68,15 @@ define([ hashes: {}, ids: {} }; + var oldIds = {}; + var oldLocks = {}; + var myUniqueOOId; var myOOId; + var sessionId = Hash.createChannelId(); + + var getId = function () { + return metadataMgr.getNetfluxId() + '-' + privateData.clientId; + }; var deleteOffline = function () { var ids = content.ids; @@ -88,11 +96,22 @@ define([ var ids = content.ids; // Check if the provided id is in the ID list return Object.keys(ids).some(function (id) { - return ooid === ids[id]; + return ooid === ids[id].ooid; + }); + }; + + var getUserIndex = function () { + var i = 1; + var ids = content.ids || {}; + Object.keys(ids).forEach(function (k) { + if (ids[k] && ids[k].index && ids[k].index >= i) { + i = ids[k].index + 1; + } }); + return i; }; - var setMyId = function (netfluxId) { + var setMyId = function () { // Remove ids for users that have left the channel deleteOffline(); var ids = content.ids; @@ -104,8 +123,13 @@ define([ myOOId = Util.createRandomInteger(); } } - var myId = (netfluxId || metadataMgr.getNetfluxId()) + '-' + privateData.clientId; - ids[myId] = myOOId; + var myId = getId(); + ids[myId] = { + ooid: myOOId, + index: getUserIndex(), + netflux: metadataMgr.getNetfluxId() + }; + oldIds = JSON.parse(JSON.stringify(ids)); APP.onLocal(); }; @@ -116,6 +140,7 @@ define([ if (content.ids[tabId]) { console.log('delete'); delete content.ids[tabId]; + delete content.locks[tabId]; APP.onLocal(); console.log(content.ids); } @@ -289,6 +314,233 @@ define([ cb(); }; + var getParticipants = function () { + var users = metadataMgr.getMetadata().users; + var i = 1; + var p = Object.keys(content.ids || {}).map(function (id) { + var nId = id.slice(0,32); + var ooId = content.ids[id].ooid; + var idx = content.ids[id].index; + if (!ooId || ooId === myOOId) { return; } + if (idx >= i) { i = idx + 1; } + return { + id: String(ooId) + idx, + idOriginal: String(ooId), + username: (users[nId] || {}).name || Messages.anonymous, + indexUser: idx, + connectionId: content.ids[id].netflux || Hash.createChannelId(), + isCloseCoAuthoring:false, + view: false + }; + }); + if (!myUniqueOOId) { myUniqueOOId = String(myOOId) + i; } + p.push({ + id: myUniqueOOId, + idOriginal: String(myOOId), + username: metadataMgr.getUserData().name || Messages.anonymous, + indexUser: i, + connectionId: metadataMgr.getNetfluxId() || Hash.createChannelId(), + isCloseCoAuthoring:false, + view: false + }); + console.log(p.filter(Boolean)); + return { + index: i, + list: p.filter(Boolean) + }; + }; + + var handleNewIds = function (o, n) { + console.log('handle nw ids'); + if (stringify(o) === stringify(n)) { return; } + console.log(n); + var p = getParticipants(); + ooChannel.send({ + type: "connectState", + participantsTimestamp: +new Date(), + participants: p.list, + waitAuth: false + }); + /* + Object.keys(n).forEach(function (id) { + var nId = id.slice(0,32); + if (!o[id]) { + console.log('new user'); + ooChannel.send({ + type: "connectState", + state: true, + user: { + id: String(n[id].ooid) + "1", + idOriginal: String(n[id].ooid), + username: (users[nId] || {}).name || Messages.anonymous, + indexUser: n[id].index, + view: false + } + }); + return; + } + }); + Object.keys(o).forEach(function (id) { + var nId = id.slice(0,32); + if (!n[id]) { + console.log('leaving user'); + ooChannel.send({ + type: "connectState", + state: false, + user: { + id: String(o[id].ooid) + "1", + idOriginal: String(o[id].ooid), + username: (users[nId] || {}).name || Messages.anonymous, + indexUser: o[id].index, + view: false + } + }); + return; + } + }); + */ + }; + var handleNewLocks = function (o, n) { + Object.keys(n).forEach(function (id) { + if (!o[id]) { + console.log('new lock'); + ooChannel.send({ + type: "getLock", + locks: getLock() + }); + return; + } + if (stringify(n[id]) !== stringify(o[id])) { + console.log('changed lock'); + ooChannel.send({ + type: "releaseLock", + locks: [o[id]] + }); + ooChannel.send({ + type: "getLock", + locks: getLock() + }); + } + }); + Object.keys(o).forEach(function (id) { + if (!n[id]) { + console.log('released lock'); + ooChannel.send({ + type: "releaseLock", + locks: [o[id]] + }); + return; + } + }); + }; + + + var handleAuth = function (obj, send) { + ooChannel.ready = true; + var changes = []; + ooChannel.queue.forEach(function (data) { + Array.prototype.push.apply(changes, data.msg.changes); + }); + var p = getParticipants(); + send({ + type: "authChanges", + changes: changes + }); + send({ + type: "auth", + result: 1, + sessionId: sessionId, //"08e77705-dc5c-477d-b73a-b1a7cbca1e9b", + // XXX add all users from chainpad + participants: p.list, + locks: [], + changes: [], + changesIndex: 0, + indexUser: p.index, + buildVersion: "5.2.6", + buildNumber: 2, + licenseType: 3, + "g_cAscSpellCheckUrl": "/spellchecker", + "settings":{"spellcheckerUrl":"/spellchecker","reconnection":{"attempts":50,"delay":2000}} + }); + send({ + type: "documentOpen", + data: {"type":"open","status":"ok","data":{"Editor.bin":obj.openCmd.url}} + }); + var last = ooChannel.queue.pop(); + ooChannel.lastHash = last.hash; + ooChannel.cpIndex += ooChannel.queue.length; + deleteOfflineLocks(); + APP.onLocal(); + handleNewLocks(oldLocks, content.locks || {}); + /*setTimeout(function () { + if (ooChannel.queue) { + ooChannel.queue.forEach(function (data) { + send(data.msg); + ooChannel.lastHash = data.hash; + ooChannel.cpIndex++; + }); + } + deleteOfflineLocks(); + APP.onLocal(); + handleNewLocks(oldLocks, content.locks || {}); + }, 200);*/ + }; + + var deleteOfflineLocks = function () { + var locks = content.locks || {}; + var users = Object.keys(metadataMgr.getMetadata().users); + console.log('delete offline locks'); + console.log(users); + Object.keys(locks).forEach(function (id) { + var nId = id.slice(0,32); + console.log(nId); + if (users.indexOf(nId) === -1) { + console.log('deleted'); + ooChannel.send({ + type: "releaseLock", + locks: [locks[id]] + }); + delete content.locks[id]; + } + }); + }; + + var getLock = function () { + return Object.keys(content.locks).map(function (id) { + return content.locks[id]; + }); + }; + + var handleLock = function (obj, send) { + console.log('handle lock'); + content.locks = content.locks || {}; + deleteOfflineLocks(); + // XXX store in chainpad + var msg = { + time: now(), + user: myUniqueOOId, + block: obj.block && obj.block[0], + } + var myId = getId(); + /*if (content.locks[myId]) { + send({ + type: "releaseLock", + locks: [content.locks[myId]] + }); + }*/ + content.locks[myId] = msg; + oldLocks = JSON.parse(JSON.stringify(content.locks)); + APP.onLocal(); + send({ + type: "getLock", + locks: getLock() + }); + //APP.realtime.onSettle(function () { + console.log(getLock()); + console.log(oldLocks); + //}); + }; + var parseChanges = function (changes) { try { changes = JSON.parse(changes); @@ -300,8 +552,8 @@ define([ docid: "fresh", change: '"' + change + '"', time: now(), - user: "test", // XXX get username - useridoriginal: "test" // get user id from worker? + user: myUniqueOOId, // XXX get username + useridoriginal: String(myOOId) // get user id from worker? }; }); }; @@ -326,37 +578,7 @@ define([ var msg, msg2; switch (obj.type) { case "auth": - ooChannel.ready = true; - send({ - type: "auth", - result: 1, - sessionId: "08e77705-dc5c-477d-b73a-b1a7cbca1e9b", - participants: [{ - id: "myid1", - idOriginal: "myid", - username: "User", - indexUser: 1, - view: false - }], - locks: [], - changes: [], - changesIndex: 0, - indexUser: 1, - "g_cAscSpellCheckUrl": "/spellchecker" - }); - send({ - type: "documentOpen", - data: {"type":"open","status":"ok","data":{"Editor.bin":obj.openCmd.url}} - }); - setTimeout(function () { - if (ooChannel.queue) { - ooChannel.queue.forEach(function (data) { - send(data.msg); - ooChannel.lastHash = data.hash; - ooChannel.cpIndex++; - }); - } - }, 2000); + handleAuth(obj, send); break; case "isSaveLock": msg = { @@ -366,16 +588,7 @@ define([ send(msg); break; case "getLock": - msg = { - type: "getLock", - locks: [{ - time: now(), - user: "myid1", - block: obj.block && obj.block[0], - sessionId: "08e77705-dc5c-477d-b73a-b1a7cbca1e9b" - }] - } - send(msg); + handleLock(obj, send); break; case "getMessages": send({ type: "message" }); @@ -385,12 +598,14 @@ define([ send({ type: "unSaveLock", index: ooChannel.cpIndex, + time: +new Date() }); + // Send the change rtChannel.sendMsg({ type: "saveChanges", changes: parseChanges(obj.changes), changesIndex: ooChannel.cpIndex || 0, - locks: [], // XXX take from userdoc? + locks: [content.locks[getId()]], // XXX take from userdoc? excelAdditionalInfo: null }, null, function (err, hash) { ooChannel.cpIndex++; @@ -398,8 +613,29 @@ define([ if (ooChannel.cpIndex % CHECKPOINT_INTERVAL === 0) { makeCheckpoint(); } + // Remove my lock + delete content.locks[getId()]; + oldLocks = JSON.parse(JSON.stringify(content.locks)); + APP.onLocal(); }); break; + case "unLockDocument": + if (obj.releaseLocks && content.locks[getId()]) { + send({ + type: "releaseLock", + locks: [content.locks[getId()]] + }); + delete content.locks[getId()]; + APP.onLocal(); + } + if (obj.isSave) { + send({ + type: "unSaveLock", + time: -1, + index: -1 + }); + } + break; } }); }); @@ -421,6 +657,7 @@ define([ "permissions": { "download": false, // FIXME: download/export is not working, so we use false // to remove the button + "print": false, } }, "documentType": file.doc, @@ -432,8 +669,8 @@ define([ } }, "user": { - "id": "myid", //"c0c3bf82-20d7-4663-bf6d-7fa39c598b1d", - "name": "User", //"John Smith" + "id": String(myOOId), //"c0c3bf82-20d7-4663-bf6d-7fa39c598b1d", + "firstname": metadataMgr.getUserData().name || Messages.anonymous, }, "mode": readOnly || lock ? "view" : "edit" }, @@ -521,7 +758,7 @@ define([ var cpNfInner; config = { - patchTransformer: ChainPad.NaiveJSONTransformer, + patchTransformer: ChainPad.SmartJSONTransformer, // cryptpad debug logging (default is 1) // logLevel: 0, validateContent: function (content) { @@ -554,7 +791,7 @@ define([ if (initializing) { return; } if (readOnly) { return; } - console.log('onLocal, data avalable'); + console.error('onLocal, data available'); // Update metadata var content = stringifyInner(); APP.realtime.contentUpdate(content); @@ -657,9 +894,9 @@ define([ } openRtChannel(function () { + setMyId(); loadDocument(newDoc); initializing = false; - setMyId(); setEditable(!readOnly); UI.removeLoadingScreen(); }); @@ -674,6 +911,18 @@ define([ metadataMgr.updateMetadata(json.metadata); } content = json.content; + console.log(content); + if (content.ids) { + handleNewIds(oldIds, content.ids); + oldIds = JSON.parse(JSON.stringify(content.ids)); + } + if (content.locks) { + console.log(content.locks); + console.log(oldLocks); + handleNewLocks(oldLocks, content.locks); + // XXX send locks to oo + oldLocks = JSON.parse(JSON.stringify(content.locks)); + } }; config.onAbort = function () { diff --git a/www/common/onlyoffice/sdkjs/cell/sdk-all-min.js b/www/common/onlyoffice/sdkjs/cell/sdk-all-min.js index 4bd4331a2..e49fb60b4 100644 --- a/www/common/onlyoffice/sdkjs/cell/sdk-all-min.js +++ b/www/common/onlyoffice/sdkjs/cell/sdk-all-min.js @@ -4186,85 +4186,87 @@ AscBrowser.convertToRetinaValue = function(value, isScale) }); }; - DocsCoApi.prototype._initSocksJs = function () { - var t = this; - var sockjs; - sockjs = this.sockjs = {}; - - 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: "5.2.6", - buildNumber: 5, - 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); + DocsCoApi.prototype._initSocksJs = function () { + var t = this; + var sockjs; + sockjs = this.sockjs = {}; + + var send = function (data) { + setTimeout(function () { + console.log(data); + sockjs.onmessage({ + data: JSON.stringify(data) + }); + }); + }; + var license = { + type: 'license', + license: { + type: 3, + mode: 0, + //light: false, + //trial: false, + rights: 1, + buildVersion: "5.2.6", + buildNumber: 2, + //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('CMD', function (obj) { send(obj); }); - }); - }); - - sockjs.onopen = function() { - t._state = ConnectionState.WaitAuth; - t.onFirstConnect(); - }; - sockjs.onopen(); - - sockjs.close = function () { - console.error('Close realtime'); - }; - - sockjs.send = function (data) { - console.log(data); - try { - var obj = JSON.parse(data); - } catch (e) { - console.error(e); - return; - } + }); + }); + + sockjs.onopen = function() { + t._state = ConnectionState.WaitAuth; + t.onFirstConnect(); + }; + sockjs.onopen(); + + sockjs.close = function () { + console.error('Close realtime'); + }; + + sockjs.send = function (data) { + console.log(data); + try { + var obj = JSON.parse(data); + } catch (e) { + console.error(e); + return; + } if (channel) { channel.event('CMD', obj); } - }; + }; sockjs.onmessage = function (e) { t._onServerMessage(e.data); }; - return sockjs; - }; + return sockjs; + }; DocsCoApi.prototype._onServerOpen = function () { if (this.reconnectTimeout) { diff --git a/www/common/onlyoffice/sdkjs/slide/sdk-all-min.js b/www/common/onlyoffice/sdkjs/slide/sdk-all-min.js index 5a1a68bee..05814ee54 100644 --- a/www/common/onlyoffice/sdkjs/slide/sdk-all-min.js +++ b/www/common/onlyoffice/sdkjs/slide/sdk-all-min.js @@ -4186,105 +4186,86 @@ AscBrowser.convertToRetinaValue = function(value, isScale) }); }; - DocsCoApi.prototype._initSocksJs = function () { - var t = this; - var sockjs; - sockjs = this.sockjs = {}; - - var send = function (data) { - setTimeout(function () { - console.log(data); - this.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; - - send(license); - - 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() { - t._state = ConnectionState.WaitAuth; - t.onFirstConnect(); - }; - sockjs.onopen(); - - sockjs.close = function () { - console.error('Close realtime'); - }; - - sockjs.send = function (data) { - console.log(data); - try { - var obj = JSON.parse(data); - } catch (e) { - console.error(e); - return; - } - var msg, msg2; - switch (obj.type) { - case 'auth': - msg = { - "type":"auth", - "result":1, - "sessionId":"08e77705-dc5c-477d-b73a-b1a7cbca1e9b", - "sessionTimeConnect":+new Date(), - "participants":[] - }; - msg2 = { - "type":"documentOpen", - "data":{"type":"open","status":"ok","data":{"Editor.bin":obj.openCmd.url}} - }; - send(msg); - send(msg2); - break; - case 'getMessages': - msg = {}; - break; - } - }; + DocsCoApi.prototype._initSocksJs = function () { + var t = this; + var sockjs; + sockjs = this.sockjs = {}; + + 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: "5.2.6", + buildNumber: 5, + 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('CMD', function (obj) { + send(obj); + }); + }); + }); + + sockjs.onopen = function() { + t._state = ConnectionState.WaitAuth; + t.onFirstConnect(); + }; + sockjs.onopen(); + + sockjs.close = function () { + console.error('Close realtime'); + }; + + sockjs.send = function (data) { + console.log(data); + try { + var obj = JSON.parse(data); + } catch (e) { + console.error(e); + return; + } + if (channel) { + channel.event('CMD', obj); + } + }; sockjs.onmessage = function (e) { t._onServerMessage(e.data); }; - return sockjs; - }; + return sockjs; + }; DocsCoApi.prototype._onServerOpen = function () { if (this.reconnectTimeout) { diff --git a/www/common/onlyoffice/sdkjs/word/sdk-all-min.js b/www/common/onlyoffice/sdkjs/word/sdk-all-min.js index 6db34ace0..fe775619f 100644 --- a/www/common/onlyoffice/sdkjs/word/sdk-all-min.js +++ b/www/common/onlyoffice/sdkjs/word/sdk-all-min.js @@ -4186,105 +4186,86 @@ AscBrowser.convertToRetinaValue = function(value, isScale) }); }; - DocsCoApi.prototype._initSocksJs = function () { - var t = this; - var sockjs; - sockjs = this.sockjs = {}; - - var send = function (data) { - setTimeout(function () { - console.log(data); - this.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; - - send(license); - - 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() { - t._state = ConnectionState.WaitAuth; - t.onFirstConnect(); - }; - sockjs.onopen(); - - sockjs.close = function () { - console.error('Close realtime'); - }; - - sockjs.send = function (data) { - console.log(data); - try { - var obj = JSON.parse(data); - } catch (e) { - console.error(e); - return; - } - var msg, msg2; - switch (obj.type) { - case 'auth': - msg = { - "type":"auth", - "result":1, - "sessionId":"08e77705-dc5c-477d-b73a-b1a7cbca1e9b", - "sessionTimeConnect":+new Date(), - "participants":[] - }; - msg2 = { - "type":"documentOpen", - "data":{"type":"open","status":"ok","data":{"Editor.bin":obj.openCmd.url}} - }; - send(msg); - send(msg2); - break; - case 'getMessages': - msg = {}; - break; - } - }; + DocsCoApi.prototype._initSocksJs = function () { + var t = this; + var sockjs; + sockjs = this.sockjs = {}; + + 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: "5.2.6", + buildNumber: 5, + 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('CMD', function (obj) { + send(obj); + }); + }); + }); + + sockjs.onopen = function() { + t._state = ConnectionState.WaitAuth; + t.onFirstConnect(); + }; + sockjs.onopen(); + + sockjs.close = function () { + console.error('Close realtime'); + }; + + sockjs.send = function (data) { + console.log(data); + try { + var obj = JSON.parse(data); + } catch (e) { + console.error(e); + return; + } + if (channel) { + channel.event('CMD', obj); + } + }; sockjs.onmessage = function (e) { t._onServerMessage(e.data); }; - return sockjs; - }; + return sockjs; + }; DocsCoApi.prototype._onServerOpen = function () { if (this.reconnectTimeout) {