You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1450 lines
52 KiB

  1. (function(){
  2. var r=function(){var e="function"==typeof require&&require,r=function(i,o,u){o||(o=0);var n=r.resolve(i,o),t=r.m[o][n];if(!t&&e){if(t=e(n))return t}else if(t&&t.c&&(o=t.c,n=t.m,t=r.m[o][t.m],!t))throw new Error('failed to require "'+n+'" from '+o);if(!t)throw new Error('failed to require "'+i+'" from '+u);return t.exports||(t.exports={},t.call(t.exports,t,t.exports,r.relative(n,o))),t.exports};return r.resolve=function(e,n){var i=e,t=e+".js",o=e+"/index.js";return r.m[n][t]&&t?t:r.m[n][o]&&o?o:i},r.relative=function(e,t){return function(n){if("."!=n.charAt(0))return r(n,t,e);var o=e.split("/"),f=n.split("/");o.pop();for(var i=0;i<f.length;i++){var u=f[i];".."==u?o.pop():"."!=u&&o.push(u)}return r(o.join("/"),t,e)}},r}();r.m = [];
  3. r.m[0] = {
  4. "Patch.js": function(module, exports, require){
  5. /*
  6. * Copyright 2014 XWiki SAS
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. var Common = require('./Common');
  22. var Operation = require('./Operation');
  23. var Sha = require('./SHA256');
  24. var Patch = module.exports;
  25. var create = Patch.create = function (parentHash) {
  26. return {
  27. type: 'Patch',
  28. operations: [],
  29. parentHash: parentHash
  30. };
  31. };
  32. var check = Patch.check = function (patch, docLength_opt) {
  33. Common.assert(patch.type === 'Patch');
  34. Common.assert(Array.isArray(patch.operations));
  35. Common.assert(/^[0-9a-f]{64}$/.test(patch.parentHash));
  36. for (var i = patch.operations.length - 1; i >= 0; i--) {
  37. Operation.check(patch.operations[i], docLength_opt);
  38. if (i > 0) {
  39. Common.assert(!Operation.shouldMerge(patch.operations[i], patch.operations[i-1]));
  40. }
  41. if (typeof(docLength_opt) === 'number') {
  42. docLength_opt += Operation.lengthChange(patch.operations[i]);
  43. }
  44. }
  45. };
  46. var toObj = Patch.toObj = function (patch) {
  47. if (Common.PARANOIA) { check(patch); }
  48. var out = new Array(patch.operations.length+1);
  49. var i;
  50. for (i = 0; i < patch.operations.length; i++) {
  51. out[i] = Operation.toObj(patch.operations[i]);
  52. }
  53. out[i] = patch.parentHash;
  54. return out;
  55. };
  56. var fromObj = Patch.fromObj = function (obj) {
  57. Common.assert(Array.isArray(obj) && obj.length > 0);
  58. var patch = create();
  59. var i;
  60. for (i = 0; i < obj.length-1; i++) {
  61. patch.operations[i] = Operation.fromObj(obj[i]);
  62. }
  63. patch.parentHash = obj[i];
  64. if (Common.PARANOIA) { check(patch); }
  65. return patch;
  66. };
  67. var hash = function (text) {
  68. return Sha.hex_sha256(text);
  69. };
  70. var addOperation = Patch.addOperation = function (patch, op) {
  71. if (Common.PARANOIA) {
  72. check(patch);
  73. Operation.check(op);
  74. }
  75. for (var i = 0; i < patch.operations.length; i++) {
  76. if (Operation.shouldMerge(patch.operations[i], op)) {
  77. op = Operation.merge(patch.operations[i], op);
  78. patch.operations.splice(i,1);
  79. if (op === null) {
  80. //console.log("operations cancelled eachother");
  81. return;
  82. }
  83. i--;
  84. } else {
  85. var out = Operation.rebase(patch.operations[i], op);
  86. if (out === op) {
  87. // op could not be rebased further, insert it here to keep the list ordered.
  88. patch.operations.splice(i,0,op);
  89. return;
  90. } else {
  91. op = out;
  92. // op was rebased, try rebasing it against the next operation.
  93. }
  94. }
  95. }
  96. patch.operations.push(op);
  97. if (Common.PARANOIA) { check(patch); }
  98. };
  99. var clone = Patch.clone = function (patch) {
  100. if (Common.PARANOIA) { check(patch); }
  101. var out = create();
  102. out.parentHash = patch.parentHash;
  103. for (var i = 0; i < patch.operations.length; i++) {
  104. out.operations[i] = Operation.clone(patch.operations[i]);
  105. }
  106. return out;
  107. };
  108. var merge = Patch.merge = function (oldPatch, newPatch) {
  109. if (Common.PARANOIA) {
  110. check(oldPatch);
  111. check(newPatch);
  112. }
  113. oldPatch = clone(oldPatch);
  114. for (var i = newPatch.operations.length-1; i >= 0; i--) {
  115. addOperation(oldPatch, newPatch.operations[i]);
  116. }
  117. return oldPatch;
  118. };
  119. var apply = Patch.apply = function (patch, doc)
  120. {
  121. if (Common.PARANOIA) {
  122. check(patch);
  123. Common.assert(typeof(doc) === 'string');
  124. Common.assert(Sha.hex_sha256(doc) === patch.parentHash);
  125. }
  126. var newDoc = doc;
  127. for (var i = patch.operations.length-1; i >= 0; i--) {
  128. newDoc = Operation.apply(patch.operations[i], newDoc);
  129. }
  130. return newDoc;
  131. };
  132. var lengthChange = Patch.lengthChange = function (patch)
  133. {
  134. if (Common.PARANOIA) { check(patch); }
  135. var out = 0;
  136. for (var i = 0; i < patch.operations.length; i++) {
  137. out += Operation.lengthChange(patch.operations[i]);
  138. }
  139. return out;
  140. };
  141. var invert = Patch.invert = function (patch, doc)
  142. {
  143. if (Common.PARANOIA) {
  144. check(patch);
  145. Common.assert(typeof(doc) === 'string');
  146. Common.assert(Sha.hex_sha256(doc) === patch.parentHash);
  147. }
  148. var rpatch = create();
  149. var newDoc = doc;
  150. for (var i = patch.operations.length-1; i >= 0; i--) {
  151. rpatch.operations[i] = Operation.invert(patch.operations[i], newDoc);
  152. newDoc = Operation.apply(patch.operations[i], newDoc);
  153. }
  154. for (var i = rpatch.operations.length-1; i >= 0; i--) {
  155. for (var j = i - 1; j >= 0; j--) {
  156. rpatch.operations[i].offset += rpatch.operations[j].toRemove;
  157. rpatch.operations[i].offset -= rpatch.operations[j].toInsert.length;
  158. }
  159. }
  160. rpatch.parentHash = Sha.hex_sha256(newDoc);
  161. if (Common.PARANOIA) { check(rpatch); }
  162. return rpatch;
  163. };
  164. var simplify = Patch.simplify = function (patch, doc, operationSimplify)
  165. {
  166. if (Common.PARANOIA) {
  167. check(patch);
  168. Common.assert(typeof(doc) === 'string');
  169. Common.assert(Sha.hex_sha256(doc) === patch.parentHash);
  170. }
  171. operationSimplify = operationSimplify || Operation.simplify;
  172. var spatch = create(patch.parentHash);
  173. var newDoc = doc;
  174. var outOps = [];
  175. var j = 0;
  176. for (var i = patch.operations.length-1; i >= 0; i--) {
  177. outOps[j] = operationSimplify(patch.operations[i], newDoc, Operation.simplify);
  178. if (outOps[j]) {
  179. newDoc = Operation.apply(outOps[j], newDoc);
  180. j++;
  181. }
  182. }
  183. spatch.operations = outOps.reverse();
  184. if (!spatch.operations[0]) {
  185. spatch.operations.shift();
  186. }
  187. if (Common.PARANOIA) {
  188. check(spatch);
  189. }
  190. return spatch;
  191. };
  192. var equals = Patch.equals = function (patchA, patchB) {
  193. if (patchA.operations.length !== patchB.operations.length) { return false; }
  194. for (var i = 0; i < patchA.operations.length; i++) {
  195. if (!Operation.equals(patchA.operations[i], patchB.operations[i])) { return false; }
  196. }
  197. return true;
  198. };
  199. var transform = Patch.transform = function (origToTransform, transformBy, doc, transformFunction) {
  200. if (Common.PARANOIA) {
  201. check(origToTransform, doc.length);
  202. check(transformBy, doc.length);
  203. Common.assert(Sha.hex_sha256(doc) === origToTransform.parentHash);
  204. }
  205. Common.assert(origToTransform.parentHash === transformBy.parentHash);
  206. var resultOfTransformBy = apply(transformBy, doc);
  207. toTransform = clone(origToTransform);
  208. var text = doc;
  209. for (var i = toTransform.operations.length-1; i >= 0; i--) {
  210. text = Operation.apply(toTransform.operations[i], text);
  211. for (var j = transformBy.operations.length-1; j >= 0; j--) {
  212. toTransform.operations[i] = Operation.transform(text,
  213. toTransform.operations[i],
  214. transformBy.operations[j],
  215. transformFunction);
  216. if (!toTransform.operations[i]) {
  217. break;
  218. }
  219. }
  220. if (Common.PARANOIA && toTransform.operations[i]) {
  221. Operation.check(toTransform.operations[i], resultOfTransformBy.length);
  222. }
  223. }
  224. var out = create(transformBy.parentHash);
  225. for (var i = toTransform.operations.length-1; i >= 0; i--) {
  226. if (toTransform.operations[i]) {
  227. addOperation(out, toTransform.operations[i]);
  228. }
  229. }
  230. out.parentHash = Sha.hex_sha256(resultOfTransformBy);
  231. if (Common.PARANOIA) {
  232. check(out, resultOfTransformBy.length);
  233. }
  234. return out;
  235. };
  236. var random = Patch.random = function (doc, opCount) {
  237. Common.assert(typeof(doc) === 'string');
  238. opCount = opCount || (Math.floor(Math.random() * 30) + 1);
  239. var patch = create(Sha.hex_sha256(doc));
  240. var docLength = doc.length;
  241. while (opCount-- > 0) {
  242. var op = Operation.random(docLength);
  243. docLength += Operation.lengthChange(op);
  244. addOperation(patch, op);
  245. }
  246. check(patch);
  247. return patch;
  248. };
  249. },
  250. "SHA256.js": function(module, exports, require){
  251. /* A JavaScript implementation of the Secure Hash Algorithm, SHA-256
  252. * Version 0.3 Copyright Angel Marin 2003-2004 - http://anmar.eu.org/
  253. * Distributed under the BSD License
  254. * Some bits taken from Paul Johnston's SHA-1 implementation
  255. */
  256. (function () {
  257. var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
  258. function safe_add (x, y) {
  259. var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  260. var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  261. return (msw << 16) | (lsw & 0xFFFF);
  262. }
  263. function S (X, n) {return ( X >>> n ) | (X << (32 - n));}
  264. function R (X, n) {return ( X >>> n );}
  265. function Ch(x, y, z) {return ((x & y) ^ ((~x) & z));}
  266. function Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));}
  267. function Sigma0256(x) {return (S(x, 2) ^ S(x, 13) ^ S(x, 22));}
  268. function Sigma1256(x) {return (S(x, 6) ^ S(x, 11) ^ S(x, 25));}
  269. function Gamma0256(x) {return (S(x, 7) ^ S(x, 18) ^ R(x, 3));}
  270. function Gamma1256(x) {return (S(x, 17) ^ S(x, 19) ^ R(x, 10));}
  271. function newArray (n) {
  272. var a = [];
  273. for (;n>0;n--) {
  274. a.push(undefined);
  275. }
  276. return a;
  277. }
  278. function core_sha256 (m, l) {
  279. var K = [0x428A2F98,0x71374491,0xB5C0FBCF,0xE9B5DBA5,0x3956C25B,0x59F111F1,0x923F82A4,0xAB1C5ED5,0xD807AA98,0x12835B01,0x243185BE,0x550C7DC3,0x72BE5D74,0x80DEB1FE,0x9BDC06A7,0xC19BF174,0xE49B69C1,0xEFBE4786,0xFC19DC6,0x240CA1CC,0x2DE92C6F,0x4A7484AA,0x5CB0A9DC,0x76F988DA,0x983E5152,0xA831C66D,0xB00327C8,0xBF597FC7,0xC6E00BF3,0xD5A79147,0x6CA6351,0x14292967,0x27B70A85,0x2E1B2138,0x4D2C6DFC,0x53380D13,0x650A7354,0x766A0ABB,0x81C2C92E,0x92722C85,0xA2BFE8A1,0xA81A664B,0xC24B8B70,0xC76C51A3,0xD192E819,0xD6990624,0xF40E3585,0x106AA070,0x19A4C116,0x1E376C08,0x2748774C,0x34B0BCB5,0x391C0CB3,0x4ED8AA4A,0x5B9CCA4F,0x682E6FF3,0x748F82EE,0x78A5636F,0x84C87814,0x8CC70208,0x90BEFFFA,0xA4506CEB,0xBEF9A3F7,0xC67178F2];
  280. var HASH = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19];
  281. var W = newArray(64);
  282. var a, b, c, d, e, f, g, h, i, j;
  283. var T1, T2;
  284. /* append padding */
  285. m[l >> 5] |= 0x80 << (24 - l % 32);
  286. m[((l + 64 >> 9) << 4) + 15] = l;
  287. for ( var i = 0; i<m.length; i+=16 ) {
  288. a = HASH[0]; b = HASH[1]; c = HASH[2]; d = HASH[3];
  289. e = HASH[4]; f = HASH[5]; g = HASH[6]; h = HASH[7];
  290. for ( var j = 0; j<64; j++) {
  291. if (j < 16) {
  292. W[j] = m[j + i];
  293. } else {
  294. W[j] = safe_add(safe_add(safe_add(Gamma1256(
  295. W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
  296. }
  297. T1 = safe_add(safe_add(safe_add(
  298. safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
  299. T2 = safe_add(Sigma0256(a), Maj(a, b, c));
  300. h = g; g = f; f = e; e = safe_add(d, T1);
  301. d = c; c = b; b = a; a = safe_add(T1, T2);
  302. }
  303. HASH[0] = safe_add(a, HASH[0]); HASH[1] = safe_add(b, HASH[1]);
  304. HASH[2] = safe_add(c, HASH[2]); HASH[3] = safe_add(d, HASH[3]);
  305. HASH[4] = safe_add(e, HASH[4]); HASH[5] = safe_add(f, HASH[5]);
  306. HASH[6] = safe_add(g, HASH[6]); HASH[7] = safe_add(h, HASH[7]);
  307. }
  308. return HASH;
  309. }
  310. function str2binb (str) {
  311. var bin = Array();
  312. var mask = (1 << chrsz) - 1;
  313. for(var i = 0; i < str.length * chrsz; i += chrsz)
  314. bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
  315. return bin;
  316. }
  317. function binb2hex (binarray) {
  318. var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
  319. var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  320. var str = "";
  321. for (var i = 0; i < binarray.length * 4; i++) {
  322. str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
  323. hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
  324. }
  325. return str;
  326. }
  327. function hex_sha256(s){
  328. return binb2hex(core_sha256(str2binb(s),s.length * chrsz));
  329. }
  330. module.exports.hex_sha256 = hex_sha256;
  331. }());
  332. },
  333. "Common.js": function(module, exports, require){
  334. /*
  335. * Copyright 2014 XWiki SAS
  336. *
  337. * This program is free software: you can redistribute it and/or modify
  338. * it under the terms of the GNU Affero General Public License as published by
  339. * the Free Software Foundation, either version 3 of the License, or
  340. * (at your option) any later version.
  341. *
  342. * This program is distributed in the hope that it will be useful,
  343. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  344. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  345. * GNU Affero General Public License for more details.
  346. *
  347. * You should have received a copy of the GNU Affero General Public License
  348. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  349. */
  350. var PARANOIA = module.exports.PARANOIA = false;
  351. /* throw errors over non-compliant messages which would otherwise be treated as invalid */
  352. var TESTING = module.exports.TESTING = true;
  353. var assert = module.exports.assert = function (expr) {
  354. if (!expr) { throw new Error("Failed assertion"); }
  355. };
  356. var isUint = module.exports.isUint = function (integer) {
  357. return (typeof(integer) === 'number') &&
  358. (Math.floor(integer) === integer) &&
  359. (integer >= 0);
  360. };
  361. var randomASCII = module.exports.randomASCII = function (length) {
  362. var content = [];
  363. for (var i = 0; i < length; i++) {
  364. content[i] = String.fromCharCode( Math.floor(Math.random()*256) % 57 + 65 );
  365. }
  366. return content.join('');
  367. };
  368. var strcmp = module.exports.strcmp = function (a, b) {
  369. if (PARANOIA && typeof(a) !== 'string') { throw new Error(); }
  370. if (PARANOIA && typeof(b) !== 'string') { throw new Error(); }
  371. return ( (a === b) ? 0 : ( (a > b) ? 1 : -1 ) );
  372. }
  373. },
  374. "Message.js": function(module, exports, require){
  375. /*
  376. * Copyright 2014 XWiki SAS
  377. *
  378. * This program is free software: you can redistribute it and/or modify
  379. * it under the terms of the GNU Affero General Public License as published by
  380. * the Free Software Foundation, either version 3 of the License, or
  381. * (at your option) any later version.
  382. *
  383. * This program is distributed in the hope that it will be useful,
  384. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  385. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  386. * GNU Affero General Public License for more details.
  387. *
  388. * You should have received a copy of the GNU Affero General Public License
  389. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  390. */
  391. var Common = require('./Common');
  392. var Operation = require('./Operation');
  393. var Patch = require('./Patch');
  394. var Sha = require('./SHA256');
  395. var Message = module.exports;
  396. var REGISTER = Message.REGISTER = 0;
  397. var REGISTER_ACK = Message.REGISTER_ACK = 1;
  398. var PATCH = Message.PATCH = 2;
  399. var DISCONNECT = Message.DISCONNECT = 3;
  400. var PING = Message.PING = 4;
  401. var PONG = Message.PONG = 5;
  402. var check = Message.check = function(msg) {
  403. Common.assert(msg.type === 'Message');
  404. Common.assert(typeof(msg.userName) === 'string');
  405. Common.assert(typeof(msg.authToken) === 'string');
  406. Common.assert(typeof(msg.channelId) === 'string');
  407. if (msg.messageType === PATCH) {
  408. Patch.check(msg.content);
  409. Common.assert(typeof(msg.lastMsgHash) === 'string');
  410. } else if (msg.messageType === PING || msg.messageType === PONG) {
  411. Common.assert(typeof(msg.lastMsgHash) === 'undefined');
  412. Common.assert(typeof(msg.content) === 'number');
  413. } else if (msg.messageType === REGISTER
  414. || msg.messageType === REGISTER_ACK
  415. || msg.messageType === DISCONNECT)
  416. {
  417. Common.assert(typeof(msg.lastMsgHash) === 'undefined');
  418. Common.assert(typeof(msg.content) === 'undefined');
  419. } else {
  420. throw new Error("invalid message type [" + msg.messageType + "]");
  421. }
  422. };
  423. var create = Message.create = function (userName, authToken, channelId, type, content, lastMsgHash) {
  424. var msg = {
  425. type: 'Message',
  426. userName: userName,
  427. authToken: authToken,
  428. channelId: channelId,
  429. messageType: type,
  430. content: content,
  431. lastMsgHash: lastMsgHash
  432. };
  433. if (Common.PARANOIA) { check(msg); }
  434. return msg;
  435. };
  436. var toString = Message.toString = function (msg) {
  437. if (Common.PARANOIA) { check(msg); }
  438. var prefix = msg.messageType + ':';
  439. var content = '';
  440. if (msg.messageType === REGISTER) {
  441. content = JSON.stringify([REGISTER]);
  442. } else if (msg.messageType === PING || msg.messageType === PONG) {
  443. content = JSON.stringify([msg.messageType, msg.content]);
  444. } else if (msg.messageType === PATCH) {
  445. content = JSON.stringify([PATCH, Patch.toObj(msg.content), msg.lastMsgHash]);
  446. }
  447. return msg.authToken.length + ":" + msg.authToken +
  448. msg.userName.length + ":" + msg.userName +
  449. msg.channelId.length + ":" + msg.channelId +
  450. content.length + ':' + content;
  451. };
  452. var fromString = Message.fromString = function (str) {
  453. var msg = str;
  454. var unameLen = msg.substring(0,msg.indexOf(':'));
  455. msg = msg.substring(unameLen.length+1);
  456. var userName = msg.substring(0,Number(unameLen));
  457. msg = msg.substring(userName.length);
  458. var channelIdLen = msg.substring(0,msg.indexOf(':'));
  459. msg = msg.substring(channelIdLen.length+1);
  460. var channelId = msg.substring(0,Number(channelIdLen));
  461. msg = msg.substring(channelId.length);
  462. var contentStrLen = msg.substring(0,msg.indexOf(':'));
  463. msg = msg.substring(contentStrLen.length+1);
  464. var contentStr = msg.substring(0,Number(contentStrLen));
  465. Common.assert(contentStr.length === Number(contentStrLen));
  466. var content = JSON.parse(contentStr);
  467. var message;
  468. if (content[0] === PATCH) {
  469. message = create(userName, '', channelId, PATCH, Patch.fromObj(content[1]), content[2]);
  470. } else if (content[0] === PING || content[0] === PONG) {
  471. message = create(userName, '', channelId, content[0], content[1]);
  472. } else {
  473. message = create(userName, '', channelId, content[0]);
  474. }
  475. // This check validates every operation in the patch.
  476. check(message);
  477. return message
  478. };
  479. var hashOf = Message.hashOf = function (msg) {
  480. if (Common.PARANOIA) { check(msg); }
  481. var authToken = msg.authToken;
  482. msg.authToken = '';
  483. var hash = Sha.hex_sha256(toString(msg));
  484. msg.authToken = authToken;
  485. return hash;
  486. };
  487. },
  488. "ChainPad.js": function(module, exports, require){
  489. /*
  490. * Copyright 2014 XWiki SAS
  491. *
  492. * This program is free software: you can redistribute it and/or modify
  493. * it under the terms of the GNU Affero General Public License as published by
  494. * the Free Software Foundation, either version 3 of the License, or
  495. * (at your option) any later version.
  496. *
  497. * This program is distributed in the hope that it will be useful,
  498. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  499. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  500. * GNU Affero General Public License for more details.
  501. *
  502. * You should have received a copy of the GNU Affero General Public License
  503. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  504. */
  505. var Common = require('./Common');
  506. var Operation = require('./Operation');
  507. var Patch = require('./Patch');
  508. var Message = require('./Message');
  509. var Sha = require('./SHA256');
  510. var ChainPad = {};
  511. // hex_sha256('')
  512. var EMPTY_STR_HASH = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
  513. var ZERO = '0000000000000000000000000000000000000000000000000000000000000000';
  514. var enterChainPad = function (realtime, func) {
  515. return function () {
  516. if (realtime.failed) { return; }
  517. func.apply(null, arguments);
  518. };
  519. };
  520. var debug = function (realtime, msg) {
  521. console.log("[" + realtime.userName + "] " + msg);
  522. };
  523. var schedule = function (realtime, func, timeout) {
  524. if (!timeout) {
  525. timeout = Math.floor(Math.random() * 2 * realtime.avgSyncTime);
  526. }
  527. var to = setTimeout(enterChainPad(realtime, function () {
  528. realtime.schedules.splice(realtime.schedules.indexOf(to), 1);
  529. func();
  530. }), timeout);
  531. realtime.schedules.push(to);
  532. return to;
  533. };
  534. var unschedule = function (realtime, schedule) {
  535. var index = realtime.schedules.indexOf(schedule);
  536. if (index > -1) {
  537. realtime.schedules.splice(index, 1);
  538. }
  539. clearTimeout(schedule);
  540. };
  541. var onMessage = function (realtime, message, callback) {
  542. if (!realtime.messageHandlers.length) {
  543. callback("no onMessage() handler registered");
  544. }
  545. for (var i = 0; i < realtime.messageHandlers.length; i++) {
  546. realtime.messageHandlers[i](message, function () {
  547. callback.apply(null, arguments);
  548. callback = function () { };
  549. });
  550. }
  551. };
  552. var sync = function (realtime) {
  553. if (Common.PARANOIA) { check(realtime); }
  554. if (realtime.syncSchedule) {
  555. unschedule(realtime, realtime.syncSchedule);
  556. realtime.syncSchedule = null;
  557. } else {
  558. // we're currently waiting on something from the server.
  559. return;
  560. }
  561. realtime.uncommitted = Patch.simplify(
  562. realtime.uncommitted, realtime.authDoc, realtime.config.operationSimplify);
  563. if (realtime.uncommitted.operations.length === 0) {
  564. //debug(realtime, "No data to sync to the server, sleeping");
  565. realtime.syncSchedule = schedule(realtime, function () { sync(realtime); });
  566. return;
  567. }
  568. var msg;
  569. if (realtime.best === realtime.initialMessage) {
  570. msg = realtime.initialMessage;
  571. } else {
  572. msg = Message.create(realtime.userName,
  573. realtime.authToken,
  574. realtime.channelId,
  575. Message.PATCH,
  576. realtime.uncommitted,
  577. realtime.best.hashOf);
  578. }
  579. var strMsg = Message.toString(msg);
  580. onMessage(realtime, strMsg, function (err) {
  581. if (err) {
  582. debug(realtime, "Posting to server failed [" + err + "]");
  583. }
  584. });
  585. var hash = Message.hashOf(msg);
  586. var timeout = schedule(realtime, function () {
  587. debug(realtime, "Failed to send message ["+hash+"] to server");
  588. sync(realtime);
  589. }, 10000 + (Math.random() * 5000));
  590. realtime.pending = {
  591. hash: hash,
  592. callback: function () {
  593. if (realtime.initialMessage && realtime.initialMessage.hashOf === hash) {
  594. debug(realtime, "initial Ack received ["+hash+"]");
  595. realtime.initialMessage = null;
  596. }
  597. unschedule(realtime, timeout);
  598. realtime.syncSchedule = schedule(realtime, function () { sync(realtime); }, 0);
  599. }
  600. };
  601. if (Common.PARANOIA) { check(realtime); }
  602. };
  603. var getMessages = function (realtime) {
  604. realtime.registered = true;
  605. /*var to = schedule(realtime, function () {
  606. throw new Error("failed to connect to the server");
  607. }, 5000);*/
  608. var msg = Message.create(realtime.userName,
  609. realtime.authToken,
  610. realtime.channelId,
  611. Message.REGISTER);
  612. onMessage(realtime, Message.toString(msg), function (err) {
  613. if (err) { throw err; }
  614. });
  615. };
  616. var sendPing = function (realtime) {
  617. realtime.pingSchedule = undefined;
  618. realtime.lastPingTime = (new Date()).getTime();
  619. var msg = Message.create(realtime.userName,
  620. realtime.authToken,
  621. realtime.channelId,
  622. Message.PING,
  623. realtime.lastPingTime);
  624. onMessage(realtime, Message.toString(msg), function (err) {
  625. if (err) { throw err; }
  626. });
  627. };
  628. var onPong = function (realtime, msg) {
  629. if (Common.PARANOIA) {
  630. Common.assert(realtime.lastPingTime === Number(msg.content));
  631. }
  632. realtime.lastPingLag = (new Date()).getTime() - Number(msg.content);
  633. realtime.lastPingTime = 0;
  634. realtime.pingSchedule =
  635. schedule(realtime, function () { sendPing(realtime); }, realtime.pingCycle);
  636. };
  637. var create = ChainPad.create = function (userName, authToken, channelId, initialState, config) {
  638. var realtime = {
  639. type: 'ChainPad',
  640. authDoc: '',
  641. config: config || {},
  642. userName: userName,
  643. authToken: authToken,
  644. channelId: channelId,
  645. /** A patch representing all uncommitted work. */
  646. uncommitted: null,
  647. uncommittedDocLength: initialState.length,
  648. patchHandlers: [],
  649. opHandlers: [],
  650. messageHandlers: [],
  651. schedules: [],
  652. syncSchedule: null,
  653. registered: false,
  654. avgSyncTime: 100,
  655. // this is only used if PARANOIA is enabled.
  656. userInterfaceContent: undefined,
  657. failed: false,
  658. // hash and callback for previously send patch, currently in flight.
  659. pending: null,
  660. messages: {},
  661. messagesByParent: {},
  662. rootMessage: null,
  663. /**
  664. * Set to the message which sets the initialState if applicable.
  665. * Reset to null after the initial message has been successfully broadcasted.
  666. */
  667. initialMessage: null,
  668. userListChangeHandlers: [],
  669. userList: [],
  670. /** The schedule() for sending pings. */
  671. pingSchedule: undefined,
  672. lastPingLag: 0,
  673. lastPingTime: 0,
  674. /** Average number of milliseconds between pings. */
  675. pingCycle: 5000
  676. };
  677. if (Common.PARANOIA) {
  678. realtime.userInterfaceContent = initialState;
  679. }
  680. var zeroPatch = Patch.create(EMPTY_STR_HASH);
  681. zeroPatch.inverseOf = Patch.invert(zeroPatch, '');
  682. zeroPatch.inverseOf.inverseOf = zeroPatch;
  683. var zeroMsg = Message.create('', '', channelId, Message.PATCH, zeroPatch, ZERO);
  684. zeroMsg.hashOf = Message.hashOf(zeroMsg);
  685. zeroMsg.parentCount = 0;
  686. realtime.messages[zeroMsg.hashOf] = zeroMsg;
  687. (realtime.messagesByParent[zeroMsg.lastMessageHash] || []).push(zeroMsg);
  688. realtime.rootMessage = zeroMsg;
  689. realtime.best = zeroMsg;
  690. if (initialState === '') {
  691. realtime.uncommitted = Patch.create(zeroPatch.inverseOf.parentHash);
  692. return realtime;
  693. }
  694. var initialOp = Operation.create(0, 0, initialState);
  695. var initialStatePatch = Patch.create(zeroPatch.inverseOf.parentHash);
  696. Patch.addOperation(initialStatePatch, initialOp);
  697. initialStatePatch.inverseOf = Patch.invert(initialStatePatch, '');
  698. initialStatePatch.inverseOf.inverseOf = initialStatePatch;
  699. // flag this patch so it can be handled specially.
  700. // Specifically, we never treat an initialStatePatch as our own,
  701. // we let it be reverted to prevent duplication of data.
  702. initialStatePatch.isInitialStatePatch = true;
  703. initialStatePatch.inverseOf.isInitialStatePatch = true;
  704. realtime.authDoc = initialState;
  705. if (Common.PARANOIA) {
  706. realtime.userInterfaceContent = initialState;
  707. }
  708. initialMessage = Message.create(realtime.userName,
  709. realtime.authToken,
  710. realtime.channelId,
  711. Message.PATCH,
  712. initialStatePatch,
  713. zeroMsg.hashOf);
  714. initialMessage.hashOf = Message.hashOf(initialMessage);
  715. initialMessage.parentCount = 1;
  716. realtime.messages[initialMessage.hashOf] = initialMessage;
  717. (realtime.messagesByParent[initialMessage.lastMessageHash] || []).push(initialMessage);
  718. realtime.best = initialMessage;
  719. realtime.uncommitted = Patch.create(initialStatePatch.inverseOf.parentHash);
  720. realtime.initialMessage = initialMessage;
  721. return realtime;
  722. };
  723. var getParent = function (realtime, message) {
  724. return message.parent = message.parent || realtime.messages[message.lastMsgHash];
  725. };
  726. var check = ChainPad.check = function(realtime) {
  727. Common.assert(realtime.type === 'ChainPad');
  728. Common.assert(typeof(realtime.authDoc) === 'string');
  729. Patch.check(realtime.uncommitted, realtime.authDoc.length);
  730. var uiDoc = Patch.apply(realtime.uncommitted, realtime.authDoc);
  731. if (uiDoc.length !== realtime.uncommittedDocLength) {
  732. Common.assert(0);
  733. }
  734. if (realtime.userInterfaceContent !== '') {
  735. Common.assert(uiDoc === realtime.userInterfaceContent);
  736. }
  737. var doc = realtime.authDoc;
  738. var patchMsg = realtime.best;
  739. Common.assert(patchMsg.content.inverseOf.parentHash === realtime.uncommitted.parentHash);
  740. var patches = [];
  741. do {
  742. patches.push(patchMsg);
  743. doc = Patch.apply(patchMsg.content.inverseOf, doc);
  744. } while ((patchMsg = getParent(realtime, patchMsg)));
  745. Common.assert(doc === '');
  746. while ((patchMsg = patches.pop())) {
  747. doc = Patch.apply(patchMsg.content, doc);
  748. }
  749. Common.assert(doc === realtime.authDoc);
  750. };
  751. var doOperation = ChainPad.doOperation = function (realtime, op) {
  752. if (Common.PARANOIA) {
  753. check(realtime);
  754. realtime.userInterfaceContent = Operation.apply(op, realtime.userInterfaceContent);
  755. }
  756. Operation.check(op, realtime.uncommittedDocLength);
  757. Patch.addOperation(realtime.uncommitted, op);
  758. realtime.uncommittedDocLength += Operation.lengthChange(op);
  759. };
  760. var isAncestorOf = function (realtime, ancestor, decendent) {
  761. if (!decendent || !ancestor) { return false; }
  762. if (ancestor === decendent) { return true; }
  763. return isAncestorOf(realtime, ancestor, getParent(realtime, decendent));
  764. };
  765. var parentCount = function (realtime, message) {
  766. if (typeof(message.parentCount) !== 'number') {
  767. message.parentCount = parentCount(realtime, getParent(realtime, message)) + 1;
  768. }
  769. return message.parentCount;
  770. };
  771. var applyPatch = function (realtime, author, patch) {
  772. if (author === realtime.userName && !patch.isInitialStatePatch) {
  773. var inverseOldUncommitted = Patch.invert(realtime.uncommitted, realtime.authDoc);
  774. var userInterfaceContent = Patch.apply(realtime.uncommitted, realtime.authDoc);
  775. if (Common.PARANOIA) {
  776. Common.assert(userInterfaceContent === realtime.userInterfaceContent);
  777. }
  778. realtime.uncommitted = Patch.merge(inverseOldUncommitted, patch);
  779. realtime.uncommitted = Patch.invert(realtime.uncommitted, userInterfaceContent);
  780. } else {
  781. realtime.uncommitted =
  782. Patch.transform(
  783. realtime.uncommitted, patch, realtime.authDoc, realtime.config.transformFunction);
  784. }
  785. realtime.uncommitted.parentHash = patch.inverseOf.parentHash;
  786. realtime.authDoc = Patch.apply(patch, realtime.authDoc);
  787. if (Common.PARANOIA) {
  788. realtime.userInterfaceContent = Patch.apply(realtime.uncommitted, realtime.authDoc);
  789. }
  790. };
  791. var revertPatch = function (realtime, author, patch) {
  792. applyPatch(realtime, author, patch.inverseOf);
  793. };
  794. var getBestChild = function (realtime, msg) {
  795. var best = msg;
  796. (realtime.messagesByParent[msg.hashOf] || []).forEach(function (child) {
  797. Common.assert(child.lastMsgHash === msg.hashOf);
  798. child = getBestChild(realtime, child);
  799. if (parentCount(realtime, child) > parentCount(realtime, best)) { best = child; }
  800. });
  801. return best;
  802. };
  803. var userListChange = function (realtime) {
  804. for (var i = 0; i < realtime.userListChangeHandlers.length; i++) {
  805. var list = [];
  806. list.push.apply(list, realtime.userList);
  807. realtime.userListChangeHandlers[i](list);
  808. }
  809. };
  810. var handleMessage = ChainPad.handleMessage = function (realtime, msgStr) {
  811. if (Common.PARANOIA) { check(realtime); }
  812. var msg = Message.fromString(msgStr);
  813. Common.assert(msg.channelId === realtime.channelId);
  814. if (msg.messageType === Message.REGISTER_ACK) {
  815. debug(realtime, "registered");
  816. realtime.registered = true;
  817. sendPing(realtime);
  818. return;
  819. }
  820. if (msg.messageType === Message.REGISTER) {
  821. realtime.userList.push(msg.userName);
  822. userListChange(realtime);
  823. return;
  824. }
  825. if (msg.messageType === Message.PONG) {
  826. onPong(realtime, msg);
  827. return;
  828. }
  829. if (msg.messageType === Message.DISCONNECT) {
  830. if (msg.userName === '') {
  831. realtime.userList = [];
  832. userListChange(realtime);
  833. return;
  834. }
  835. var idx = realtime.userList.indexOf(msg.userName);
  836. if (Common.PARANOIA) { Common.assert(idx > -1); }
  837. if (idx > -1) {
  838. realtime.userList.splice(idx, 1);
  839. userListChange(realtime);
  840. }
  841. return;
  842. }
  843. // otherwise it's a disconnect.
  844. if (msg.messageType !== Message.PATCH) { return; }
  845. msg.hashOf = Message.hashOf(msg);
  846. if (realtime.pending && realtime.pending.hash === msg.hashOf) {
  847. realtime.pending.callback();
  848. realtime.pending = null;
  849. }
  850. if (realtime.messages[msg.hashOf]) {
  851. debug(realtime, "Patch [" + msg.hashOf + "] is already known");
  852. if (Common.PARANOIA) { check(realtime); }
  853. return;
  854. }
  855. realtime.messages[msg.hashOf] = msg;
  856. (realtime.messagesByParent[msg.lastMsgHash] =
  857. realtime.messagesByParent[msg.lastMsgHash] || []).push(msg);
  858. if (!isAncestorOf(realtime, realtime.rootMessage, msg)) {
  859. // we'll probably find the missing parent later.
  860. debug(realtime, "Patch [" + msg.hashOf + "] not connected to root");
  861. if (Common.PARANOIA) { check(realtime); }
  862. return;
  863. }
  864. // of this message fills in a hole in the chain which makes another patch better, swap to the
  865. // best child of this patch since longest chain always wins.
  866. msg = getBestChild(realtime, msg);
  867. var patch = msg.content;
  868. // Find the ancestor of this patch which is in the main chain, reverting as necessary
  869. var toRevert = [];
  870. var commonAncestor = realtime.best;
  871. if (!isAncestorOf(realtime, realtime.best, msg)) {
  872. var pcBest = parentCount(realtime, realtime.best);
  873. var pcMsg = parentCount(realtime, msg);
  874. if (pcBest < pcMsg
  875. || (pcBest === pcMsg
  876. && Common.strcmp(realtime.best.hashOf, msg.hashOf) > 0))
  877. {
  878. // switch chains
  879. while (commonAncestor && !isAncestorOf(realtime, commonAncestor, msg)) {
  880. toRevert.push(commonAncestor);
  881. commonAncestor = getParent(realtime, commonAncestor);
  882. }
  883. Common.assert(commonAncestor);
  884. } else {
  885. debug(realtime, "Patch [" + msg.hashOf + "] chain is ["+pcMsg+"] best chain is ["+pcBest+"]");
  886. if (Common.PARANOIA) { check(realtime); }
  887. return;
  888. }
  889. }
  890. // Find the parents of this patch which are not in the main chain.
  891. var toApply = [];
  892. var current = msg;
  893. do {
  894. toApply.unshift(current);
  895. current = getParent(realtime, current);
  896. Common.assert(current);
  897. } while (current !== commonAncestor);
  898. var authDocAtTimeOfPatch = realtime.authDoc;
  899. for (var i = 0; i < toRevert.length; i++) {
  900. authDocAtTimeOfPatch = Patch.apply(toRevert[i].content.inverseOf, authDocAtTimeOfPatch);
  901. }
  902. // toApply.length-1 because we do not want to apply the new patch.
  903. for (var i = 0; i < toApply.length-1; i++) {
  904. if (typeof(toApply[i].content.inverseOf) === 'undefined') {
  905. toApply[i].content.inverseOf = Patch.invert(toApply[i].content, authDocAtTimeOfPatch);
  906. toApply[i].content.inverseOf.inverseOf = toApply[i].content;
  907. }
  908. authDocAtTimeOfPatch = Patch.apply(toApply[i].content, authDocAtTimeOfPatch);
  909. }
  910. if (Sha.hex_sha256(authDocAtTimeOfPatch) !== patch.parentHash) {
  911. debug(realtime, "patch [" + msg.hashOf + "] parentHash is not valid");
  912. if (Common.PARANOIA) { check(realtime); }
  913. if (Common.TESTING) { throw new Error(); }
  914. delete realtime.messages[msg.hashOf];
  915. return;
  916. }
  917. var simplePatch =
  918. Patch.simplify(patch, authDocAtTimeOfPatch, realtime.config.operationSimplify);
  919. if (!Patch.equals(simplePatch, patch)) {
  920. debug(realtime, "patch [" + msg.hashOf + "] can be simplified");
  921. if (Common.PARANOIA) { check(realtime); }
  922. if (Common.TESTING) { throw new Error(); }
  923. delete realtime.messages[msg.hashOf];
  924. return;
  925. }
  926. patch.inverseOf = Patch.invert(patch, authDocAtTimeOfPatch);
  927. patch.inverseOf.inverseOf = patch;
  928. realtime.uncommitted = Patch.simplify(
  929. realtime.uncommitted, realtime.authDoc, realtime.config.operationSimplify);
  930. var oldUserInterfaceContent = Patch.apply(realtime.uncommitted, realtime.authDoc);
  931. if (Common.PARANOIA) {
  932. Common.assert(oldUserInterfaceContent === realtime.userInterfaceContent);
  933. }
  934. // Derive the patch for the user's uncommitted work
  935. var uncommittedPatch = Patch.invert(realtime.uncommitted, realtime.authDoc);
  936. for (var i = 0; i < toRevert.length; i++) {
  937. debug(realtime, "reverting [" + toRevert[i].hashOf + "]");
  938. uncommittedPatch = Patch.merge(uncommittedPatch, toRevert[i].content.inverseOf);
  939. revertPatch(realtime, toRevert[i].userName, toRevert[i].content);
  940. }
  941. for (var i = 0; i < toApply.length; i++) {
  942. debug(realtime, "applying [" + toApply[i].hashOf + "]");
  943. uncommittedPatch = Patch.merge(uncommittedPatch, toApply[i].content);
  944. applyPatch(realtime, toApply[i].userName, toApply[i].content);
  945. }
  946. uncommittedPatch = Patch.merge(uncommittedPatch, realtime.uncommitted);
  947. uncommittedPatch = Patch.simplify(
  948. uncommittedPatch, oldUserInterfaceContent, realtime.config.operationSimplify);
  949. realtime.uncommittedDocLength += Patch.lengthChange(uncommittedPatch);
  950. realtime.best = msg;
  951. if (Common.PARANOIA) {
  952. // apply the uncommittedPatch to the userInterface content.
  953. var newUserInterfaceContent = Patch.apply(uncommittedPatch, oldUserInterfaceContent);
  954. Common.assert(realtime.userInterfaceContent.length === realtime.uncommittedDocLength);
  955. Common.assert(newUserInterfaceContent === realtime.userInterfaceContent);
  956. }
  957. // push the uncommittedPatch out to the user interface.
  958. for (var i = 0; i < realtime.patchHandlers.length; i++) {
  959. realtime.patchHandlers[i](uncommittedPatch);
  960. }
  961. if (realtime.opHandlers.length) {
  962. for (var i = uncommittedPatch.operations.length-1; i >= 0; i--) {
  963. for (var j = 0; j < realtime.opHandlers.length; j++) {
  964. realtime.opHandlers[j](uncommittedPatch.operations[i]);
  965. }
  966. }
  967. }
  968. if (Common.PARANOIA) { check(realtime); }
  969. };
  970. module.exports.create = function (userName, authToken, channelId, initialState, conf) {
  971. Common.assert(typeof(userName) === 'string');
  972. Common.assert(typeof(authToken) === 'string');
  973. Common.assert(typeof(channelId) === 'string');
  974. Common.assert(typeof(initialState) === 'string');
  975. var realtime = ChainPad.create(userName, authToken, channelId, initialState, conf);
  976. return {
  977. onPatch: enterChainPad(realtime, function (handler) {
  978. Common.assert(typeof(handler) === 'function');
  979. realtime.patchHandlers.push(handler);
  980. }),
  981. onRemove: enterChainPad(realtime, function (handler) {
  982. Common.assert(typeof(handler) === 'function');
  983. realtime.opHandlers.unshift(function (op) {
  984. if (op.toRemove > 0) { handler(op.offset, op.toRemove); }
  985. });
  986. }),
  987. onInsert: enterChainPad(realtime, function (handler) {
  988. Common.assert(typeof(handler) === 'function');
  989. realtime.opHandlers.push(function (op) {
  990. if (op.toInsert.length > 0) { handler(op.offset, op.toInsert); }
  991. });
  992. }),
  993. remove: enterChainPad(realtime, function (offset, numChars) {
  994. doOperation(realtime, Operation.create(offset, numChars, ''));
  995. }),
  996. insert: enterChainPad(realtime, function (offset, str) {
  997. doOperation(realtime, Operation.create(offset, 0, str));
  998. }),
  999. onMessage: enterChainPad(realtime, function (handler) {
  1000. Common.assert(typeof(handler) === 'function');
  1001. realtime.messageHandlers.push(handler);
  1002. }),
  1003. message: enterChainPad(realtime, function (message) {
  1004. handleMessage(realtime, message);
  1005. }),
  1006. start: enterChainPad(realtime, function () {
  1007. getMessages(realtime);
  1008. if (realtime.syncSchedule) { unschedule(realtime, realtime.syncSchedule); }
  1009. realtime.syncSchedule = schedule(realtime, function () { sync(realtime); });
  1010. }),
  1011. abort: enterChainPad(realtime, function () {
  1012. realtime.schedules.forEach(function (s) { clearTimeout(s) });
  1013. }),
  1014. sync: enterChainPad(realtime, function () {
  1015. sync(realtime);
  1016. }),
  1017. getAuthDoc: function () { return realtime.authDoc; },
  1018. getUserDoc: function () { return Patch.apply(realtime.uncommitted, realtime.authDoc); },
  1019. onUserListChange: enterChainPad(realtime, function (handler) {
  1020. Common.assert(typeof(handler) === 'function');
  1021. realtime.userListChangeHandlers.push(handler);
  1022. }),
  1023. getLag: function () {
  1024. if (realtime.lastPingTime) {
  1025. return { waiting:1, lag: (new Date()).getTime() - realtime.lastPingTime };
  1026. }
  1027. return { waiting:0, lag: realtime.lastPingLag };
  1028. }
  1029. };
  1030. };
  1031. },
  1032. "Operation.js": function(module, exports, require){
  1033. /*
  1034. * Copyright 2014 XWiki SAS
  1035. *
  1036. * This program is free software: you can redistribute it and/or modify
  1037. * it under the terms of the GNU Affero General Public License as published by
  1038. * the Free Software Foundation, either version 3 of the License, or
  1039. * (at your option) any later version.
  1040. *
  1041. * This program is distributed in the hope that it will be useful,
  1042. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1043. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  1044. * GNU Affero General Public License for more details.
  1045. *
  1046. * You should have received a copy of the GNU Affero General Public License
  1047. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  1048. */
  1049. var Common = require('./Common');
  1050. var Operation = module.exports;
  1051. var check = Operation.check = function (op, docLength_opt) {
  1052. Common.assert(op.type === 'Operation');
  1053. Common.assert(Common.isUint(op.offset));
  1054. Common.assert(Common.isUint(op.toRemove));
  1055. Common.assert(typeof(op.toInsert) === 'string');
  1056. Common.assert(op.toRemove > 0 || op.toInsert.length > 0);
  1057. Common.assert(typeof(docLength_opt) !== 'number' || op.offset + op.toRemove <= docLength_opt);
  1058. };
  1059. var create = Operation.create = function (offset, toRemove, toInsert) {
  1060. var out = {
  1061. type: 'Operation',
  1062. offset: offset || 0,
  1063. toRemove: toRemove || 0,
  1064. toInsert: toInsert || '',
  1065. };
  1066. if (Common.PARANOIA) { check(out); }
  1067. return out;
  1068. };
  1069. var toObj = Operation.toObj = function (op) {
  1070. if (Common.PARANOIA) { check(op); }
  1071. return [op.offset,op.toRemove,op.toInsert];
  1072. };
  1073. var fromObj = Operation.fromObj = function (obj) {
  1074. Common.assert(Array.isArray(obj) && obj.length === 3);
  1075. return create(obj[0], obj[1], obj[2]);
  1076. };
  1077. var clone = Operation.clone = function (op) {
  1078. return create(op.offset, op.toRemove, op.toInsert);
  1079. };
  1080. /**
  1081. * @param op the operation to apply.
  1082. * @param doc the content to apply the operation on
  1083. */
  1084. var apply = Operation.apply = function (op, doc)
  1085. {
  1086. if (Common.PARANOIA) {
  1087. check(op);
  1088. Common.assert(typeof(doc) === 'string');
  1089. Common.assert(op.offset + op.toRemove <= doc.length);
  1090. }
  1091. return doc.substring(0,op.offset) + op.toInsert + doc.substring(op.offset + op.toRemove);
  1092. };
  1093. var invert = Operation.invert = function (op, doc) {
  1094. if (Common.PARANOIA) {
  1095. check(op);
  1096. Common.assert(typeof(doc) === 'string');
  1097. Common.assert(op.offset + op.toRemove <= doc.length);
  1098. }
  1099. var rop = clone(op);
  1100. rop.toInsert = doc.substring(op.offset, op.offset + op.toRemove);
  1101. rop.toRemove = op.toInsert.length;
  1102. return rop;
  1103. };
  1104. var simplify = Operation.simplify = function (op, doc) {
  1105. if (Common.PARANOIA) {
  1106. check(op);
  1107. Common.assert(typeof(doc) === 'string');
  1108. Common.assert(op.offset + op.toRemove <= doc.length);
  1109. }
  1110. var rop = invert(op, doc);
  1111. op = clone(op);
  1112. var minLen = Math.min(op.toInsert.length, rop.toInsert.length);
  1113. var i;
  1114. for (i = 0; i < minLen && rop.toInsert[i] === op.toInsert[i]; i++) ;
  1115. op.offset += i;
  1116. op.toRemove -= i;
  1117. op.toInsert = op.toInsert.substring(i);
  1118. rop.toInsert = rop.toInsert.substring(i);
  1119. if (rop.toInsert.length === op.toInsert.length) {
  1120. for (i = rop.toInsert.length-1; i >= 0 && rop.toInsert[i] === op.toInsert[i]; i--) ;
  1121. op.toInsert = op.toInsert.substring(0, i+1);
  1122. op.toRemove = i+1;
  1123. }
  1124. if (op.toRemove === 0 && op.toInsert.length === 0) { return null; }
  1125. return op;
  1126. };
  1127. var equals = Operation.equals = function (opA, opB) {
  1128. return (opA.toRemove === opB.toRemove
  1129. && opA.toInsert === opB.toInsert
  1130. && opA.offset === opB.offset);
  1131. };
  1132. var lengthChange = Operation.lengthChange = function (op)
  1133. {
  1134. if (Common.PARANOIA) { check(op); }
  1135. return op.toInsert.length - op.toRemove;
  1136. };
  1137. /*
  1138. * @return the merged operation OR null if the result of the merger is a noop.
  1139. */
  1140. var merge = Operation.merge = function (oldOpOrig, newOpOrig) {
  1141. if (Common.PARANOIA) {
  1142. check(newOpOrig);
  1143. check(oldOpOrig);
  1144. }
  1145. var newOp = clone(newOpOrig);
  1146. var oldOp = clone(oldOpOrig);
  1147. var offsetDiff = newOp.offset - oldOp.offset;
  1148. if (newOp.toRemove > 0) {
  1149. var origOldInsert = oldOp.toInsert;
  1150. oldOp.toInsert = (
  1151. oldOp.toInsert.substring(0,offsetDiff)
  1152. + oldOp.toInsert.substring(offsetDiff + newOp.toRemove)
  1153. );
  1154. newOp.toRemove -= (origOldInsert.length - oldOp.toInsert.length);
  1155. if (newOp.toRemove < 0) { newOp.toRemove = 0; }
  1156. oldOp.toRemove += newOp.toRemove;
  1157. newOp.toRemove = 0;
  1158. }
  1159. if (offsetDiff < 0) {
  1160. oldOp.offset += offsetDiff;
  1161. oldOp.toInsert = newOp.toInsert + oldOp.toInsert;
  1162. } else if (oldOp.toInsert.length === offsetDiff) {
  1163. oldOp.toInsert = oldOp.toInsert + newOp.toInsert;
  1164. } else if (oldOp.toInsert.length > offsetDiff) {
  1165. oldOp.toInsert = (
  1166. oldOp.toInsert.substring(0,offsetDiff)
  1167. + newOp.toInsert
  1168. + oldOp.toInsert.substring(offsetDiff)
  1169. );
  1170. } else {
  1171. throw new Error("should never happen\n" +
  1172. JSON.stringify([oldOpOrig,newOpOrig], null, ' '));
  1173. }
  1174. if (oldOp.toInsert === '' && oldOp.toRemove === 0) {
  1175. return null;
  1176. }
  1177. if (Common.PARANOIA) { check(oldOp); }
  1178. return oldOp;
  1179. };
  1180. /**
  1181. * If the new operation deletes what the old op inserted or inserts content in the middle of
  1182. * the old op's content or if they abbut one another, they should be merged.
  1183. */
  1184. var shouldMerge = Operation.shouldMerge = function (oldOp, newOp) {
  1185. if (Common.PARANOIA) {
  1186. check(oldOp);
  1187. check(newOp);
  1188. }
  1189. if (newOp.offset < oldOp.offset) {
  1190. return (oldOp.offset <= (newOp.offset + newOp.toRemove));
  1191. } else {
  1192. return (newOp.offset <= (oldOp.offset + oldOp.toInsert.length));
  1193. }
  1194. };
  1195. /**
  1196. * Rebase newOp against oldOp.
  1197. *
  1198. * @param oldOp the eariler operation to have happened.
  1199. * @param newOp the later operation to have happened (in time).
  1200. * @return either the untouched newOp if it need not be rebased,
  1201. * the rebased clone of newOp if it needs rebasing, or
  1202. * null if newOp and oldOp must be merged.
  1203. */
  1204. var rebase = Operation.rebase = function (oldOp, newOp) {
  1205. if (Common.PARANOIA) {
  1206. check(oldOp);
  1207. check(newOp);
  1208. }
  1209. if (newOp.offset < oldOp.offset) { return newOp; }
  1210. newOp = clone(newOp);
  1211. newOp.offset += oldOp.toRemove;
  1212. newOp.offset -= oldOp.toInsert.length;
  1213. return newOp;
  1214. };
  1215. /**
  1216. * this is a lossy and dirty algorithm, everything else is nice but transformation
  1217. * has to be lossy because both operations have the same base and they diverge.
  1218. * This could be made nicer and/or tailored to a specific data type.
  1219. *
  1220. * @param toTransform the operation which is converted *MUTATED*.
  1221. * @param transformBy an existing operation which also has the same base.
  1222. * @return toTransform *or* null if the result is a no-op.
  1223. */
  1224. var transform0 = Operation.transform0 = function (text, toTransform, transformBy) {
  1225. if (toTransform.offset > transformBy.offset) {
  1226. if (toTransform.offset > transformBy.offset + transformBy.toRemove) {
  1227. // simple rebase
  1228. toTransform.offset -= transformBy.toRemove;
  1229. toTransform.offset += transformBy.toInsert.length;
  1230. return toTransform;
  1231. }
  1232. // goto the end, anything you deleted that they also deleted should be skipped.
  1233. var newOffset = transformBy.offset + transformBy.toInsert.length;
  1234. toTransform.toRemove = 0; //-= (newOffset - toTransform.offset);
  1235. if (toTransform.toRemove < 0) { toTransform.toRemove = 0; }
  1236. toTransform.offset = newOffset;
  1237. if (toTransform.toInsert.length === 0 && toTransform.toRemove === 0) {
  1238. return null;
  1239. }
  1240. return toTransform;
  1241. }
  1242. if (toTransform.offset + toTransform.toRemove < transformBy.offset) {
  1243. return toTransform;
  1244. }
  1245. toTransform.toRemove = transformBy.offset - toTransform.offset;
  1246. if (toTransform.toInsert.length === 0 && toTransform.toRemove === 0) {
  1247. return null;
  1248. }
  1249. return toTransform;
  1250. };
  1251. /**
  1252. * @param toTransform the operation which is converted
  1253. * @param transformBy an existing operation which also has the same base.
  1254. * @return a modified clone of toTransform *or* toTransform itself if no change was made.
  1255. */
  1256. var transform = Operation.transform = function (text, toTransform, transformBy, transformFunction) {
  1257. if (Common.PARANOIA) {
  1258. check(toTransform);
  1259. check(transformBy);
  1260. }
  1261. transformFunction = transformFunction || transform0;
  1262. toTransform = clone(toTransform);
  1263. var result = transformFunction(text, toTransform, transformBy);
  1264. if (Common.PARANOIA && result) { check(result); }
  1265. return result;
  1266. };
  1267. /** Used for testing. */
  1268. var random = Operation.random = function (docLength) {
  1269. Common.assert(Common.isUint(docLength));
  1270. var offset = Math.floor(Math.random() * 100000000 % docLength) || 0;
  1271. var toRemove = Math.floor(Math.random() * 100000000 % (docLength - offset)) || 0;
  1272. var toInsert = '';
  1273. do {
  1274. var toInsert = Common.randomASCII(Math.floor(Math.random() * 20));
  1275. } while (toRemove === 0 && toInsert === '');
  1276. return create(offset, toRemove, toInsert);
  1277. };
  1278. }
  1279. };
  1280. ChainPad = r("ChainPad.js");}());