sn-collab-editor 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,21 +12,27 @@ class EditChannel < ApplicationCable::Channel
12
12
  def retrieve(data)
13
13
  doc = Collab::Document.find_by_uuid(data['doc_id'])
14
14
  ActionCable.server.broadcast("doc_#{doc.uuid}_#{params['client_id']}", {
15
- :message => doc.content,
16
- :iv => doc.iv,
17
- :auth => doc.auth,
15
+ :patches => doc.patches,
18
16
  :client_id => data['client_id'],
19
- :retrieve => true
20
- })
17
+ :initial_retrieve => true
18
+ })
21
19
  end
22
20
 
23
21
  def post(data)
24
- message = data['message']
25
22
  doc = Collab::Document.find_by_uuid(data['doc_id'])
26
- doc.content = message
27
- doc.iv = data["iv"]
28
- doc.auth = data["auth"]
23
+
24
+ edit_token = data["edit_token"]
25
+ if edit_token != doc.edit_token
26
+ puts "Edit token not authorized, returning"
27
+ return
28
+ end
29
+
30
+ patch = doc.patches.create({
31
+ :content => data['content'],
32
+ :iv => data["iv"],
33
+ :auth => data["auth"]
34
+ })
29
35
  doc.save
30
- EditChannel.broadcast_to(doc, {:message => message, :iv => data["iv"], :auth => data["auth"], :client_id => data['client_id']})
36
+ EditChannel.broadcast_to(doc, {:patch => patch, :client_id => data['client_id']})
31
37
  end
32
38
  end
@@ -24,11 +24,11 @@ module Collab
24
24
  end
25
25
 
26
26
  def new
27
- puts "\n\nCreating new document\n\n"
28
27
  @doc = Document.new
29
28
  @doc.uuid = SecureRandom.uuid
29
+ @doc.edit_token = Digest::SHA256.hexdigest(SecureRandom.random_bytes())[0,24]
30
30
  @doc.save
31
- redirect_to(url)
31
+ redirect_to(url + "?et=#{@doc.edit_token}")
32
32
  end
33
33
 
34
34
  end
@@ -1,4 +1,5 @@
1
1
  module Collab
2
2
  class Document < ApplicationRecord
3
+ has_many :patches
3
4
  end
4
5
  end
@@ -0,0 +1,5 @@
1
+ module Collab
2
+ class Patch < ApplicationRecord
3
+
4
+ end
5
+ end
@@ -2,11 +2,18 @@
2
2
 
3
3
  <div id="wrapper">
4
4
  <% if @doc %>
5
- <div id="url">
6
- <strong>Sharing URL</strong>:
7
- <span><%= @url %></span><span id="url-key"></span>
5
+
6
+ <div id="viewing-url-wrapper" class="url-wrapper">
7
+ <span class="label">Viewing URL</span>:
8
+ <a id="viewing-url" class="url" target="_blank"></a>
9
+ </div>
10
+
11
+ <div id="editing-url-wrapper" class="url-wrapper">
12
+ <span class="label">Editing URL</span>:
13
+ <a id="editing-url" class="url" target="_blank"></a>
8
14
  </div>
9
- <textarea id="editor"></textarea>
15
+
16
+ <textarea id="editor" name="editor"></textarea>
10
17
  <% else %>
11
18
 
12
19
  <% end %>
@@ -3,9 +3,14 @@
3
3
  <head>
4
4
  <title>Standard Notes Collaborative Editor</title>
5
5
  <%= stylesheet_link_tag "collab/application", media: "all" %>
6
+ <%= stylesheet_link_tag "codemirror" %>
6
7
  <%= javascript_include_tag "collab/application" %>
7
8
  <%= javascript_include_tag "aes" %>
8
9
  <%= javascript_include_tag "hmac-sha256" %>
10
+ <%= javascript_include_tag "chainpad" %>
11
+ <%= javascript_include_tag "codemirror" %>
12
+ <%= javascript_include_tag "TextPatcher" %>
13
+ <%= javascript_include_tag "markdown" %>
9
14
  <%= csrf_meta_tags %>
10
15
  </head>
11
16
  <body>
@@ -1,11 +1,16 @@
1
1
  class CreateCollabDocuments < ActiveRecord::Migration[5.0]
2
2
  def change
3
3
  create_table :collab_documents do |t|
4
- t.text :content
5
- t.string :iv
6
- t.string :auth
7
4
  t.string :uuid
5
+ t.string :edit_token
8
6
  t.timestamps
9
7
  end
8
+
9
+ create_table :collab_patches do |t|
10
+ t.text :content, :limit => 16.megabytes - 1
11
+ t.string :iv
12
+ t.string :auth
13
+ t.integer :document_id
14
+ end
10
15
  end
11
16
  end
data/lib/collab/engine.rb CHANGED
@@ -2,7 +2,6 @@ module Collab
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Collab
4
4
 
5
- # config.assets.paths << File.expand_path("../../../vendor/assets/javascripts", __FILE__)
6
- config.assets.precompile += %w( aes.js hmac-sha256.js )
5
+ config.assets.precompile += %w( aes.js hmac-sha256.js chainpad.js codemirror.js codemirror.css TextPatcher markdown )
7
6
  end
8
7
  end
@@ -1,3 +1,3 @@
1
1
  module Collab
2
- VERSION = '0.1.6'
2
+ VERSION = '0.1.7'
3
3
  end
@@ -0,0 +1,154 @@
1
+ (function () {
2
+
3
+ var TextPatcher = {};
4
+
5
+ /* diff takes two strings, the old content, and the desired content
6
+ it returns the difference between these two strings in the form
7
+ of an 'Operation' (as defined in chainpad.js).
8
+
9
+ diff is purely functional.
10
+ */
11
+ var diff = TextPatcher.diff = function (oldval, newval) {
12
+ // Strings are immutable and have reference equality. I think this test is O(1), so its worth doing.
13
+ if (oldval === newval) {
14
+ return;
15
+ }
16
+
17
+ var commonStart = 0;
18
+ while (oldval.charAt(commonStart) === newval.charAt(commonStart)) {
19
+ commonStart++;
20
+ }
21
+
22
+ var commonEnd = 0;
23
+ while (oldval.charAt(oldval.length - 1 - commonEnd) === newval.charAt(newval.length - 1 - commonEnd) &&
24
+ commonEnd + commonStart < oldval.length && commonEnd + commonStart < newval.length) {
25
+ commonEnd++;
26
+ }
27
+
28
+ var toRemove = 0;
29
+ var toInsert = '';
30
+
31
+ /* throw some assertions in here before dropping patches into the realtime */
32
+ if (oldval.length !== commonStart + commonEnd) {
33
+ toRemove = oldval.length - commonStart - commonEnd;
34
+ }
35
+ if (newval.length !== commonStart + commonEnd) {
36
+ toInsert = newval.slice(commonStart, newval.length - commonEnd);
37
+ }
38
+
39
+ return {
40
+ type: 'Operation',
41
+ offset: commonStart,
42
+ toInsert: toInsert,
43
+ toRemove: toRemove
44
+ };
45
+ };
46
+
47
+ /* patch accepts a realtime facade and an operation (which might be falsey)
48
+ it applies the operation to the realtime as components (remove/insert)
49
+
50
+ patch has no return value, and operates solely through side effects on
51
+ the realtime facade.
52
+ */
53
+ var patch = TextPatcher.patch = function (ctx, op) {
54
+ if (!op) { return; }
55
+
56
+ if (ctx.patch) {
57
+ ctx.patch(op.offset, op.toRemove, op.toInsert);
58
+ } else {
59
+ console.log("chainpad.remove and chainpad.insert are deprecated. "+
60
+ "update your chainpad installation to the latest version.");
61
+ if (op.toRemove) { ctx.remove(op.offset, op.toRemove); }
62
+ if (op.toInsert) { ctx.insert(op.offset, op.toInsert); }
63
+ }
64
+ };
65
+
66
+ /* format has the same signature as log, but doesn't log to the console
67
+ use it to get the pretty version of a diff */
68
+ var format = TextPatcher.format = function (text, op) {
69
+ return op?{
70
+ insert: op.toInsert,
71
+ remove: text.slice(op.offset, op.offset + op.toRemove)
72
+ }: { insert: '', remove: '' };
73
+ };
74
+
75
+ /* log accepts a string and an operation, and prints an object to the console
76
+ the object will display the content which is to be removed, and the content
77
+ which will be inserted in its place.
78
+
79
+ log is useful for debugging, but can otherwise be disabled.
80
+ */
81
+ var log = TextPatcher.log = function (text, op) {
82
+ if (!op) { return; }
83
+ console.log(format(text, op));
84
+ };
85
+
86
+ /* applyChange takes:
87
+ ctx: the context (aka the realtime)
88
+ oldval: the old value
89
+ newval: the new value
90
+
91
+ it performs a diff on the two values, and generates patches
92
+ which are then passed into `ctx.remove` and `ctx.insert`.
93
+
94
+ Due to its reliance on patch, applyChange has side effects on the supplied
95
+ realtime facade.
96
+ */
97
+ var applyChange = TextPatcher.applyChange = function(ctx, oldval, newval, logging) {
98
+ var op = diff(oldval, newval);
99
+ if (logging) { log(oldval, op); }
100
+ patch(ctx, op);
101
+ };
102
+
103
+ var transformCursor = TextPatcher.transformCursor = function (cursor, op) {
104
+ if (!op) { return cursor; }
105
+ var pos = op.offset;
106
+ var remove = op.toRemove;
107
+ var insert = op.toInsert.length;
108
+ if (typeof cursor === 'undefined') { return; }
109
+ if (typeof remove === 'number' && pos < cursor) {
110
+ cursor -= Math.min(remove, cursor - pos);
111
+ }
112
+ if (typeof insert === 'number' && pos < cursor) {
113
+ cursor += insert;
114
+ }
115
+ return cursor;
116
+ };
117
+
118
+ var create = TextPatcher.create = function(config) {
119
+ var ctx = config.realtime;
120
+ var logging = config.logging;
121
+
122
+ // initial state will always fail the !== check in genop.
123
+ // because nothing will equal this object
124
+ var content = {};
125
+
126
+ // *** remote -> local changes
127
+ ctx.onPatch(function(pos, length) {
128
+ content = ctx.getUserDoc();
129
+ });
130
+
131
+ // propogate()
132
+ return function (newContent, force) {
133
+ if (newContent !== content || force) {
134
+ applyChange(ctx, ctx.getUserDoc(), newContent, logging);
135
+ if (ctx.getUserDoc() !== newContent) {
136
+ console.log("Expected that: `ctx.getUserDoc() === newContent`!");
137
+ }
138
+ else { content = ctx.getUserDoc(); }
139
+ return true;
140
+ }
141
+ return false;
142
+ };
143
+ };
144
+
145
+ if (typeof(module) !== 'undefined' && module.exports) {
146
+ module.exports = TextPatcher;
147
+ } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
148
+ define(function () {
149
+ return TextPatcher;
150
+ });
151
+ } else {
152
+ window.TextPatcher = TextPatcher;
153
+ }
154
+ }());
@@ -0,0 +1,1588 @@
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
+
25
+ var Patch = module.exports;
26
+
27
+ var create = Patch.create = function (parentHash) {
28
+ return {
29
+ type: 'Patch',
30
+ operations: [],
31
+ parentHash: parentHash,
32
+ isCheckpoint: false
33
+ };
34
+ };
35
+
36
+ var check = Patch.check = function (patch, docLength_opt) {
37
+ Common.assert(patch.type === 'Patch');
38
+ Common.assert(Array.isArray(patch.operations));
39
+ Common.assert(/^[0-9a-f]{64}$/.test(patch.parentHash));
40
+ for (var i = patch.operations.length - 1; i >= 0; i--) {
41
+ Operation.check(patch.operations[i], docLength_opt);
42
+ if (i > 0) {
43
+ Common.assert(!Operation.shouldMerge(patch.operations[i], patch.operations[i-1]));
44
+ }
45
+ if (typeof(docLength_opt) === 'number') {
46
+ docLength_opt += Operation.lengthChange(patch.operations[i]);
47
+ }
48
+ }
49
+ if (patch.isCheckpoint) {
50
+ Common.assert(patch.operations.length === 1);
51
+ Common.assert(patch.operations[0].offset === 0);
52
+ if (typeof(docLength_opt) === 'number') {
53
+ Common.assert(!docLength_opt || patch.operations[0].toRemove === docLength_opt);
54
+ }
55
+ }
56
+ };
57
+
58
+ var toObj = Patch.toObj = function (patch) {
59
+ if (Common.PARANOIA) { check(patch); }
60
+ var out = new Array(patch.operations.length+1);
61
+ var i;
62
+ for (i = 0; i < patch.operations.length; i++) {
63
+ out[i] = Operation.toObj(patch.operations[i]);
64
+ }
65
+ out[i] = patch.parentHash;
66
+ return out;
67
+ };
68
+
69
+ var fromObj = Patch.fromObj = function (obj) {
70
+ Common.assert(Array.isArray(obj) && obj.length > 0);
71
+ var patch = create();
72
+ var i;
73
+ for (i = 0; i < obj.length-1; i++) {
74
+ patch.operations[i] = Operation.fromObj(obj[i]);
75
+ }
76
+ patch.parentHash = obj[i];
77
+ if (Common.PARANOIA) { check(patch); }
78
+ return patch;
79
+ };
80
+
81
+ var hash = function (text) {
82
+ return Sha.hex_sha256(text);
83
+ };
84
+
85
+ var addOperation = Patch.addOperation = function (patch, op) {
86
+ if (Common.PARANOIA) {
87
+ check(patch);
88
+ Operation.check(op);
89
+ }
90
+ for (var i = 0; i < patch.operations.length; i++) {
91
+ if (Operation.shouldMerge(patch.operations[i], op)) {
92
+ op = Operation.merge(patch.operations[i], op);
93
+ patch.operations.splice(i,1);
94
+ if (op === null) {
95
+ //console.log("operations cancelled eachother");
96
+ return;
97
+ }
98
+ i--;
99
+ } else {
100
+ var out = Operation.rebase(patch.operations[i], op);
101
+ if (out === op) {
102
+ // op could not be rebased further, insert it here to keep the list ordered.
103
+ patch.operations.splice(i,0,op);
104
+ return;
105
+ } else {
106
+ op = out;
107
+ // op was rebased, try rebasing it against the next operation.
108
+ }
109
+ }
110
+ }
111
+ patch.operations.push(op);
112
+ if (Common.PARANOIA) { check(patch); }
113
+ };
114
+
115
+ var createCheckpoint = Patch.createCheckpoint =
116
+ function (parentContent, checkpointContent, parentContentHash_opt)
117
+ {
118
+ var op = Operation.create(0, parentContent.length, checkpointContent);
119
+ if (Common.PARANOIA && parentContentHash_opt) {
120
+ Common.assert(parentContentHash_opt === hash(parentContent));
121
+ }
122
+ parentContentHash_opt = parentContentHash_opt || hash(parentContent);
123
+ var out = create(parentContentHash_opt);
124
+ addOperation(out, op);
125
+ out.isCheckpoint = true;
126
+ return out;
127
+ };
128
+
129
+ var clone = Patch.clone = function (patch) {
130
+ if (Common.PARANOIA) { check(patch); }
131
+ var out = create();
132
+ out.parentHash = patch.parentHash;
133
+ for (var i = 0; i < patch.operations.length; i++) {
134
+ out.operations[i] = Operation.clone(patch.operations[i]);
135
+ }
136
+ return out;
137
+ };
138
+
139
+ var merge = Patch.merge = function (oldPatch, newPatch) {
140
+ if (Common.PARANOIA) {
141
+ check(oldPatch);
142
+ check(newPatch);
143
+ }
144
+ if (oldPatch.isCheckpoint) {
145
+ Common.assert(newPatch.parentHash === oldPatch.parentHash);
146
+ if (newPatch.isCheckpoint) {
147
+ return create(oldPatch.parentHash)
148
+ }
149
+ return clone(newPatch);
150
+ } else if (newPatch.isCheckpoint) {
151
+ return clone(oldPatch);
152
+ }
153
+ oldPatch = clone(oldPatch);
154
+ for (var i = newPatch.operations.length-1; i >= 0; i--) {
155
+ addOperation(oldPatch, newPatch.operations[i]);
156
+ }
157
+ return oldPatch;
158
+ };
159
+
160
+ var apply = Patch.apply = function (patch, doc)
161
+ {
162
+ if (Common.PARANOIA) {
163
+ check(patch);
164
+ Common.assert(typeof(doc) === 'string');
165
+ Common.assert(Sha.hex_sha256(doc) === patch.parentHash);
166
+ }
167
+ var newDoc = doc;
168
+ for (var i = patch.operations.length-1; i >= 0; i--) {
169
+ newDoc = Operation.apply(patch.operations[i], newDoc);
170
+ }
171
+ return newDoc;
172
+ };
173
+
174
+ var lengthChange = Patch.lengthChange = function (patch)
175
+ {
176
+ if (Common.PARANOIA) { check(patch); }
177
+ var out = 0;
178
+ for (var i = 0; i < patch.operations.length; i++) {
179
+ out += Operation.lengthChange(patch.operations[i]);
180
+ }
181
+ return out;
182
+ };
183
+
184
+ var invert = Patch.invert = function (patch, doc)
185
+ {
186
+ if (Common.PARANOIA) {
187
+ check(patch);
188
+ Common.assert(typeof(doc) === 'string');
189
+ Common.assert(Sha.hex_sha256(doc) === patch.parentHash);
190
+ }
191
+ var rpatch = create();
192
+ var newDoc = doc;
193
+ for (var i = patch.operations.length-1; i >= 0; i--) {
194
+ rpatch.operations[i] = Operation.invert(patch.operations[i], newDoc);
195
+ newDoc = Operation.apply(patch.operations[i], newDoc);
196
+ }
197
+ for (var i = rpatch.operations.length-1; i >= 0; i--) {
198
+ for (var j = i - 1; j >= 0; j--) {
199
+ rpatch.operations[i].offset += rpatch.operations[j].toRemove;
200
+ rpatch.operations[i].offset -= rpatch.operations[j].toInsert.length;
201
+ }
202
+ }
203
+ rpatch.parentHash = Sha.hex_sha256(newDoc);
204
+ rpatch.isCheckpoint = patch.isCheckpoint;
205
+ if (Common.PARANOIA) { check(rpatch); }
206
+ return rpatch;
207
+ };
208
+
209
+ var simplify = Patch.simplify = function (patch, doc, operationSimplify)
210
+ {
211
+ if (Common.PARANOIA) {
212
+ check(patch);
213
+ Common.assert(typeof(doc) === 'string');
214
+ Common.assert(Sha.hex_sha256(doc) === patch.parentHash);
215
+ }
216
+ operationSimplify = operationSimplify || Operation.simplify;
217
+ var spatch = create(patch.parentHash);
218
+ var newDoc = doc;
219
+ var outOps = [];
220
+ var j = 0;
221
+ for (var i = patch.operations.length-1; i >= 0; i--) {
222
+ outOps[j] = operationSimplify(patch.operations[i], newDoc, Operation.simplify);
223
+ if (outOps[j]) {
224
+ newDoc = Operation.apply(outOps[j], newDoc);
225
+ j++;
226
+ }
227
+ }
228
+ spatch.operations = outOps.reverse();
229
+ if (!spatch.operations[0]) {
230
+ spatch.operations.shift();
231
+ }
232
+ if (Common.PARANOIA) {
233
+ check(spatch);
234
+ }
235
+ return spatch;
236
+ };
237
+
238
+ var equals = Patch.equals = function (patchA, patchB) {
239
+ if (patchA.operations.length !== patchB.operations.length) { return false; }
240
+ for (var i = 0; i < patchA.operations.length; i++) {
241
+ if (!Operation.equals(patchA.operations[i], patchB.operations[i])) { return false; }
242
+ }
243
+ return true;
244
+ };
245
+
246
+ var isCheckpointOp = function (op, text) {
247
+ return op.offset === 0 && op.toRemove === text.length && op.toInsert === text;
248
+ };
249
+
250
+ var transform = Patch.transform = function (origToTransform, transformBy, doc, transformFunction) {
251
+ if (Common.PARANOIA) {
252
+ check(origToTransform, doc.length);
253
+ check(transformBy, doc.length);
254
+ Common.assert(Sha.hex_sha256(doc) === origToTransform.parentHash);
255
+ }
256
+ Common.assert(origToTransform.parentHash === transformBy.parentHash);
257
+ var resultOfTransformBy = apply(transformBy, doc);
258
+
259
+ var toTransform = clone(origToTransform);
260
+ var text = doc;
261
+ for (var i = toTransform.operations.length-1; i >= 0; i--) {
262
+ if (isCheckpointOp(toTransform.operations[i], text)) { continue; }
263
+ for (var j = transformBy.operations.length-1; j >= 0; j--) {
264
+ if (isCheckpointOp(transformBy.operations[j], text)) { console.log('cpo'); continue; }
265
+ if (Common.DEBUG) {
266
+ console.log(
267
+ ['TRANSFORM', text, toTransform.operations[i], transformBy.operations[j]]
268
+ );
269
+ }
270
+ try {
271
+ toTransform.operations[i] = Operation.transform(text,
272
+ toTransform.operations[i],
273
+ transformBy.operations[j],
274
+ transformFunction);
275
+ } catch (e) {
276
+ console.error("The pluggable transform function threw an error, " +
277
+ "failing operational transformation");
278
+ return create(Sha.hex_sha256(resultOfTransformBy));
279
+ }
280
+ if (!toTransform.operations[i]) {
281
+ break;
282
+ }
283
+ }
284
+ if (Common.PARANOIA && toTransform.operations[i]) {
285
+ Operation.check(toTransform.operations[i], resultOfTransformBy.length);
286
+ }
287
+ }
288
+ var out = create(transformBy.parentHash);
289
+ for (var i = toTransform.operations.length-1; i >= 0; i--) {
290
+ if (toTransform.operations[i]) {
291
+ addOperation(out, toTransform.operations[i]);
292
+ }
293
+ }
294
+
295
+ out.parentHash = Sha.hex_sha256(resultOfTransformBy);
296
+
297
+ if (Common.PARANOIA) {
298
+ check(out, resultOfTransformBy.length);
299
+ }
300
+ return out;
301
+ };
302
+
303
+ var random = Patch.random = function (doc, opCount) {
304
+ Common.assert(typeof(doc) === 'string');
305
+ opCount = opCount || (Math.floor(Math.random() * 30) + 1);
306
+ var patch = create(Sha.hex_sha256(doc));
307
+ var docLength = doc.length;
308
+ while (opCount-- > 0) {
309
+ var op = Operation.random(docLength);
310
+ docLength += Operation.lengthChange(op);
311
+ addOperation(patch, op);
312
+ }
313
+ check(patch);
314
+ return patch;
315
+ };
316
+
317
+ },
318
+ "SHA256.js": function(module, exports, require){
319
+ /* A JavaScript implementation of the Secure Hash Algorithm, SHA-256
320
+ * Version 0.3 Copyright Angel Marin 2003-2004 - http://anmar.eu.org/
321
+ * Distributed under the BSD License
322
+ * Some bits taken from Paul Johnston's SHA-1 implementation
323
+ */
324
+ (function () {
325
+ var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
326
+ function safe_add (x, y) {
327
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
328
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
329
+ return (msw << 16) | (lsw & 0xFFFF);
330
+ }
331
+ function S (X, n) {return ( X >>> n ) | (X << (32 - n));}
332
+ function R (X, n) {return ( X >>> n );}
333
+ function Ch(x, y, z) {return ((x & y) ^ ((~x) & z));}
334
+ function Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));}
335
+ function Sigma0256(x) {return (S(x, 2) ^ S(x, 13) ^ S(x, 22));}
336
+ function Sigma1256(x) {return (S(x, 6) ^ S(x, 11) ^ S(x, 25));}
337
+ function Gamma0256(x) {return (S(x, 7) ^ S(x, 18) ^ R(x, 3));}
338
+ function Gamma1256(x) {return (S(x, 17) ^ S(x, 19) ^ R(x, 10));}
339
+ function newArray (n) {
340
+ var a = [];
341
+ for (;n>0;n--) {
342
+ a.push(undefined);
343
+ }
344
+ return a;
345
+ }
346
+ function core_sha256 (m, l) {
347
+ 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];
348
+ var HASH = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19];
349
+ var W = newArray(64);
350
+ var a, b, c, d, e, f, g, h, i, j;
351
+ var T1, T2;
352
+ /* append padding */
353
+ m[l >> 5] |= 0x80 << (24 - l % 32);
354
+ m[((l + 64 >> 9) << 4) + 15] = l;
355
+ for ( var i = 0; i<m.length; i+=16 ) {
356
+ a = HASH[0]; b = HASH[1]; c = HASH[2]; d = HASH[3];
357
+ e = HASH[4]; f = HASH[5]; g = HASH[6]; h = HASH[7];
358
+ for ( var j = 0; j<64; j++) {
359
+ if (j < 16) {
360
+ W[j] = m[j + i];
361
+ } else {
362
+ W[j] = safe_add(safe_add(safe_add(Gamma1256(
363
+ W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
364
+ }
365
+ T1 = safe_add(safe_add(safe_add(
366
+ safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
367
+ T2 = safe_add(Sigma0256(a), Maj(a, b, c));
368
+ h = g; g = f; f = e; e = safe_add(d, T1);
369
+ d = c; c = b; b = a; a = safe_add(T1, T2);
370
+ }
371
+ HASH[0] = safe_add(a, HASH[0]); HASH[1] = safe_add(b, HASH[1]);
372
+ HASH[2] = safe_add(c, HASH[2]); HASH[3] = safe_add(d, HASH[3]);
373
+ HASH[4] = safe_add(e, HASH[4]); HASH[5] = safe_add(f, HASH[5]);
374
+ HASH[6] = safe_add(g, HASH[6]); HASH[7] = safe_add(h, HASH[7]);
375
+ }
376
+ return HASH;
377
+ }
378
+ function str2binb (str) {
379
+ var bin = Array();
380
+ var mask = (1 << chrsz) - 1;
381
+ for(var i = 0; i < str.length * chrsz; i += chrsz)
382
+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
383
+ return bin;
384
+ }
385
+ function binb2hex (binarray) {
386
+ var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
387
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
388
+ var str = "";
389
+ for (var i = 0; i < binarray.length * 4; i++) {
390
+ str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
391
+ hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
392
+ }
393
+ return str;
394
+ }
395
+ function hex_sha256(s){
396
+ return binb2hex(core_sha256(str2binb(s),s.length * chrsz));
397
+ }
398
+ module.exports.hex_sha256 = hex_sha256;
399
+ }());
400
+
401
+ },
402
+ "Common.js": function(module, exports, require){
403
+ /*
404
+ * Copyright 2014 XWiki SAS
405
+ *
406
+ * This program is free software: you can redistribute it and/or modify
407
+ * it under the terms of the GNU Affero General Public License as published by
408
+ * the Free Software Foundation, either version 3 of the License, or
409
+ * (at your option) any later version.
410
+ *
411
+ * This program is distributed in the hope that it will be useful,
412
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
413
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
414
+ * GNU Affero General Public License for more details.
415
+ *
416
+ * You should have received a copy of the GNU Affero General Public License
417
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
418
+ */
419
+
420
+ var DEBUG = module.exports.debug =
421
+ (typeof(localStorage) !== 'undefined' && localStorage['ChainPad_DEBUG']);
422
+
423
+ var PARANOIA = module.exports.PARANOIA =
424
+ (typeof(localStorage) !== 'undefined' && localStorage['ChainPad_PARANOIA']);
425
+
426
+ /* Good testing but slooooooooooow */
427
+ var VALIDATE_ENTIRE_CHAIN_EACH_MSG = module.exports.VALIDATE_ENTIRE_CHAIN_EACH_MSG =
428
+ (typeof(localStorage) !== 'undefined' && localStorage['ChainPad_VALIDATE_ENTIRE_CHAIN_EACH_MSG']);
429
+
430
+ /* throw errors over non-compliant messages which would otherwise be treated as invalid */
431
+ var TESTING = module.exports.TESTING =
432
+ (typeof(localStorage) !== 'undefined' && localStorage['ChainPad_TESTING']);
433
+
434
+ var assert = module.exports.assert = function (expr) {
435
+ if (!expr) { throw new Error("Failed assertion"); }
436
+ };
437
+
438
+ var isUint = module.exports.isUint = function (integer) {
439
+ return (typeof(integer) === 'number') &&
440
+ (Math.floor(integer) === integer) &&
441
+ (integer >= 0);
442
+ };
443
+
444
+ var randomASCII = module.exports.randomASCII = function (length) {
445
+ var content = [];
446
+ for (var i = 0; i < length; i++) {
447
+ content[i] = String.fromCharCode( Math.floor(Math.random()*256) % 57 + 65 );
448
+ }
449
+ return content.join('');
450
+ };
451
+
452
+ var strcmp = module.exports.strcmp = function (a, b) {
453
+ if (PARANOIA && typeof(a) !== 'string') { throw new Error(); }
454
+ if (PARANOIA && typeof(b) !== 'string') { throw new Error(); }
455
+ return ( (a === b) ? 0 : ( (a > b) ? 1 : -1 ) );
456
+ }
457
+
458
+ },
459
+ "Message.js": function(module, exports, require){
460
+ /*
461
+ * Copyright 2014 XWiki SAS
462
+ *
463
+ * This program is free software: you can redistribute it and/or modify
464
+ * it under the terms of the GNU Affero General Public License as published by
465
+ * the Free Software Foundation, either version 3 of the License, or
466
+ * (at your option) any later version.
467
+ *
468
+ * This program is distributed in the hope that it will be useful,
469
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
470
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
471
+ * GNU Affero General Public License for more details.
472
+ *
473
+ * You should have received a copy of the GNU Affero General Public License
474
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
475
+ */
476
+ var Common = require('./Common');
477
+ var Operation = require('./Operation');
478
+ var Patch = require('./Patch');
479
+ var Sha = require('./SHA256');
480
+
481
+ var Message = module.exports;
482
+
483
+ var REGISTER = Message.REGISTER = 0;
484
+ var REGISTER_ACK = Message.REGISTER_ACK = 1;
485
+ var PATCH = Message.PATCH = 2;
486
+ var DISCONNECT = Message.DISCONNECT = 3;
487
+ var CHECKPOINT = Message.CHECKPOINT = 4;
488
+
489
+ var check = Message.check = function(msg) {
490
+ Common.assert(msg.type === 'Message');
491
+ if (msg.messageType === PATCH || msg.messageType === CHECKPOINT) {
492
+ Patch.check(msg.content);
493
+ Common.assert(typeof(msg.lastMsgHash) === 'string');
494
+ } else {
495
+ throw new Error("invalid message type [" + msg.messageType + "]");
496
+ }
497
+ };
498
+
499
+ var create = Message.create = function (type, content, lastMsgHash) {
500
+ var msg = {
501
+ type: 'Message',
502
+ messageType: type,
503
+ content: content,
504
+ lastMsgHash: lastMsgHash
505
+ };
506
+ if (Common.PARANOIA) { check(msg); }
507
+ return msg;
508
+ };
509
+
510
+ var toString = Message.toString = function (msg) {
511
+ if (Common.PARANOIA) { check(msg); }
512
+ if (msg.messageType === PATCH || msg.messageType === CHECKPOINT) {
513
+ return JSON.stringify([msg.messageType, Patch.toObj(msg.content), msg.lastMsgHash]);
514
+ } else {
515
+ throw new Error();
516
+ }
517
+ };
518
+
519
+ var discardBencode = function (msg, arr) {
520
+ var len = msg.substring(0,msg.indexOf(':'));
521
+ msg = msg.substring(len.length+1);
522
+ var value = msg.substring(0,Number(len));
523
+ msg = msg.substring(value.length);
524
+
525
+ if (arr) { arr.push(value); }
526
+ return msg;
527
+ };
528
+
529
+ var fromString = Message.fromString = function (str) {
530
+ var m = JSON.parse(str);
531
+ if (m[0] !== CHECKPOINT && m[0] !== PATCH) { throw new Error("invalid message type " + m[0]); }
532
+ var msg = create(m[0], Patch.fromObj(m[1]), m[2]);
533
+ if (m[0] === CHECKPOINT) { msg.content.isCheckpoint = true; }
534
+ return msg;
535
+ };
536
+
537
+ var hashOf = Message.hashOf = function (msg) {
538
+ if (Common.PARANOIA) { check(msg); }
539
+ var hash = Sha.hex_sha256(toString(msg));
540
+ return hash;
541
+ };
542
+
543
+ },
544
+ "ChainPad.js": function(module, exports, require){
545
+ /*
546
+ * Copyright 2014 XWiki SAS
547
+ *
548
+ * This program is free software: you can redistribute it and/or modify
549
+ * it under the terms of the GNU Affero General Public License as published by
550
+ * the Free Software Foundation, either version 3 of the License, or
551
+ * (at your option) any later version.
552
+ *
553
+ * This program is distributed in the hope that it will be useful,
554
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
555
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
556
+ * GNU Affero General Public License for more details.
557
+ *
558
+ * You should have received a copy of the GNU Affero General Public License
559
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
560
+ */
561
+ var Common = module.exports.Common = require('./Common');
562
+ var Operation = module.exports.Operation = require('./Operation');
563
+ var Patch = module.exports.Patch = require('./Patch');
564
+ var Message = module.exports.Message = require('./Message');
565
+ var Sha = module.exports.Sha = require('./SHA256');
566
+
567
+ var ChainPad = {};
568
+
569
+ // hex_sha256('')
570
+ var EMPTY_STR_HASH = module.exports.EMPTY_STR_HASH =
571
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
572
+ var ZERO = '0000000000000000000000000000000000000000000000000000000000000000';
573
+
574
+ // Default number of patches between checkpoints (patches older than this will be pruned)
575
+ // default for realtime.config.checkpointInterval
576
+ var DEFAULT_CHECKPOINT_INTERVAL = 50;
577
+
578
+ // Default number of milliseconds to wait before syncing to the server
579
+ var DEFAULT_AVERAGE_SYNC_MILLISECONDS = 300;
580
+
581
+ // By default, we allow checkpoints at any place but if this is set true, we will blow up on chains
582
+ // which have checkpoints not where we expect them to be.
583
+ var DEFAULT_STRICT_CHECKPOINT_VALIDATION = false;
584
+
585
+ var enterChainPad = function (realtime, func) {
586
+ return function () {
587
+ if (realtime.failed) { return; }
588
+ func.apply(null, arguments);
589
+ };
590
+ };
591
+
592
+ var debug = function (realtime, msg) {
593
+ if (realtime.logLevel > 0) {
594
+ console.log("[" + realtime.userName + "] " + msg);
595
+ }
596
+ };
597
+
598
+ var warn = function (realtime, msg) {
599
+ if (realtime.logLevel > 0) {
600
+ console.error("[" + realtime.userName + "] " + msg);
601
+ }
602
+ };
603
+
604
+ var schedule = function (realtime, func, timeout) {
605
+ if (realtime.aborted) { return; }
606
+ if (!timeout) {
607
+ timeout = Math.floor(Math.random() * 2 * realtime.config.avgSyncMilliseconds);
608
+ }
609
+ var to = setTimeout(enterChainPad(realtime, function () {
610
+ realtime.schedules.splice(realtime.schedules.indexOf(to), 1);
611
+ func();
612
+ }), timeout);
613
+ realtime.schedules.push(to);
614
+ return to;
615
+ };
616
+
617
+ var unschedule = function (realtime, schedule) {
618
+ var index = realtime.schedules.indexOf(schedule);
619
+ if (index > -1) {
620
+ realtime.schedules.splice(index, 1);
621
+ }
622
+ clearTimeout(schedule);
623
+ };
624
+
625
+ var onMessage = function (realtime, message, callback) {
626
+ if (!realtime.messageHandlers.length) {
627
+ callback("no onMessage() handler registered");
628
+ }
629
+ for (var i = 0; i < realtime.messageHandlers.length; i++) {
630
+ realtime.messageHandlers[i](message, function () {
631
+ callback.apply(null, arguments);
632
+ callback = function () { };
633
+ });
634
+ }
635
+ };
636
+
637
+ var sendMessage = function (realtime, msg, callback) {
638
+ var strMsg = Message.toString(msg);
639
+
640
+ onMessage(realtime, strMsg, function (err) {
641
+ if (err) {
642
+ debug(realtime, "Posting to server failed [" + err + "]");
643
+ realtime.pending = null;
644
+ } else {
645
+ var pending = realtime.pending;
646
+ realtime.pending = null;
647
+ Common.assert(pending.hash === msg.hashOf);
648
+ handleMessage(realtime, strMsg, true);
649
+ pending.callback();
650
+ }
651
+ });
652
+
653
+ msg.hashOf = msg.hashOf || Message.hashOf(msg);
654
+
655
+ var timeout = schedule(realtime, function () {
656
+ debug(realtime, "Failed to send message [" + msg.hashOf + "] to server");
657
+ sync(realtime);
658
+ }, 10000 + (Math.random() * 5000));
659
+
660
+ if (realtime.pending) { throw new Error("there is already a pending message"); }
661
+ realtime.pending = {
662
+ hash: msg.hashOf,
663
+ callback: function () {
664
+ unschedule(realtime, timeout);
665
+ realtime.syncSchedule = schedule(realtime, function () { sync(realtime); }, 0);
666
+ callback();
667
+ }
668
+ };
669
+ if (Common.PARANOIA) { check(realtime); }
670
+ };
671
+
672
+ var sync = function (realtime) {
673
+ if (Common.PARANOIA) { check(realtime); }
674
+ if (realtime.syncSchedule && !realtime.pending) {
675
+ unschedule(realtime, realtime.syncSchedule);
676
+ realtime.syncSchedule = null;
677
+ } else {
678
+ //debug(realtime, "already syncing...");
679
+ // we're currently waiting on something from the server.
680
+ return;
681
+ }
682
+
683
+ realtime.uncommitted = Patch.simplify(
684
+ realtime.uncommitted, realtime.authDoc, realtime.config.operationSimplify);
685
+
686
+ if (realtime.uncommitted.operations.length === 0) {
687
+ //debug(realtime, "No data to sync to the server, sleeping");
688
+ realtime.syncSchedule = schedule(realtime, function () { sync(realtime); });
689
+ return;
690
+ }
691
+
692
+ if (((parentCount(realtime, realtime.best) + 1) % realtime.config.checkpointInterval) === 0) {
693
+ var best = realtime.best;
694
+ debug(realtime, "Sending checkpoint");
695
+ var cpp = Patch.createCheckpoint(realtime.authDoc,
696
+ realtime.authDoc,
697
+ realtime.best.content.inverseOf.parentHash);
698
+ var cp = Message.create(Message.CHECKPOINT, cpp, realtime.best.hashOf);
699
+ sendMessage(realtime, cp, function () {
700
+ debug(realtime, "Checkpoint sent and accepted");
701
+ });
702
+ return;
703
+ }
704
+
705
+ var msg;
706
+ if (realtime.setContentPatch) {
707
+ msg = realtime.setContentPatch;
708
+ } else {
709
+ msg = Message.create(Message.PATCH, realtime.uncommitted, realtime.best.hashOf);
710
+ }
711
+
712
+ sendMessage(realtime, msg, function () {
713
+ //debug(realtime, "patch sent");
714
+ if (realtime.setContentPatch) {
715
+ debug(realtime, "initial Ack received [" + msg.hashOf + "]");
716
+ realtime.setContentPatch = null;
717
+ }
718
+ });
719
+ };
720
+
721
+ var storeMessage = function (realtime, msg) {
722
+ Common.assert(msg.lastMsgHash);
723
+ Common.assert(msg.hashOf);
724
+ realtime.messages[msg.hashOf] = msg;
725
+ (realtime.messagesByParent[msg.lastMsgHash] =
726
+ realtime.messagesByParent[msg.lastMsgHash] || []).push(msg);
727
+ };
728
+
729
+ var forgetMessage = function (realtime, msg) {
730
+ Common.assert(msg.lastMsgHash);
731
+ Common.assert(msg.hashOf);
732
+ delete realtime.messages[msg.hashOf];
733
+ var list = realtime.messagesByParent[msg.lastMsgHash];
734
+ Common.assert(list.indexOf(msg) > -1);
735
+ list.splice(list.indexOf(msg), 1);
736
+ if (list.length === 0) {
737
+ delete realtime.messagesByParent[msg.lastMsgHash];
738
+ }
739
+ var children = realtime.messagesByParent[msg.hashOf];
740
+ if (children) {
741
+ for (var i = 0; i < children.length; i++) {
742
+ delete children[i].parent;
743
+ }
744
+ }
745
+ };
746
+
747
+ var create = ChainPad.create = function (config) {
748
+ config = config || {};
749
+ var initialState = config.initialState || '';
750
+ config.checkpointInterval = config.checkpointInterval || DEFAULT_CHECKPOINT_INTERVAL;
751
+ config.avgSyncMilliseconds = config.avgSyncMilliseconds || DEFAULT_AVERAGE_SYNC_MILLISECONDS;
752
+ config.strictCheckpointValidation =
753
+ config.strictCheckpointValidation || DEFAULT_STRICT_CHECKPOINT_VALIDATION;
754
+
755
+ var realtime = {
756
+ type: 'ChainPad',
757
+
758
+ authDoc: '',
759
+
760
+ config: config,
761
+
762
+ logLevel: (typeof(config.logLevel) === 'number') ? config.logLevel : 1,
763
+
764
+ /** A patch representing all uncommitted work. */
765
+ uncommitted: null,
766
+
767
+ uncommittedDocLength: initialState.length,
768
+
769
+ patchHandlers: [],
770
+ changeHandlers: [],
771
+
772
+ messageHandlers: [],
773
+
774
+ schedules: [],
775
+ aborted: false,
776
+
777
+ syncSchedule: null,
778
+
779
+ registered: false,
780
+
781
+ // this is only used if PARANOIA is enabled.
782
+ userInterfaceContent: undefined,
783
+
784
+ // If we want to set the content to a particular thing, this patch will be sent across the
785
+ // wire. If the patch is not accepted we will not try to recover it. This is used for
786
+ // setting initial state.
787
+ setContentPatch: null,
788
+
789
+ failed: false,
790
+
791
+ // hash and callback for previously send patch, currently in flight.
792
+ pending: null,
793
+
794
+ messages: {},
795
+ messagesByParent: {},
796
+
797
+ rootMessage: null,
798
+
799
+ onSettle: [],
800
+
801
+ userName: config.userName || 'anonymous',
802
+ };
803
+
804
+ var zeroPatch = Patch.create(EMPTY_STR_HASH);
805
+ zeroPatch.inverseOf = Patch.invert(zeroPatch, '');
806
+ zeroPatch.inverseOf.inverseOf = zeroPatch;
807
+ var zeroMsg = Message.create(Message.PATCH, zeroPatch, ZERO);
808
+ zeroMsg.hashOf = Message.hashOf(zeroMsg);
809
+ zeroMsg.parentCount = 0;
810
+ zeroMsg.isInitialMessage = true;
811
+ storeMessage(realtime, zeroMsg);
812
+ realtime.rootMessage = zeroMsg;
813
+ realtime.best = zeroMsg;
814
+
815
+ if (initialState !== '') {
816
+ var initPatch = Patch.create(EMPTY_STR_HASH);
817
+ Patch.addOperation(initPatch, Operation.create(0, 0, initialState));
818
+ initPatch.inverseOf = Patch.invert(initPatch, '');
819
+ initPatch.inverseOf.inverseOf = initPatch;
820
+ var initMsg = Message.create(Message.PATCH, initPatch, zeroMsg.hashOf);
821
+ initMsg.hashOf = Message.hashOf(initMsg);
822
+ initMsg.isInitialMessage = true;
823
+ storeMessage(realtime, initMsg);
824
+ realtime.best = initMsg;
825
+ realtime.authDoc = initialState;
826
+ realtime.setContentPatch = initMsg;
827
+ }
828
+ realtime.uncommitted = Patch.create(realtime.best.content.inverseOf.parentHash);
829
+
830
+ if (Common.PARANOIA) {
831
+ realtime.userInterfaceContent = initialState;
832
+ }
833
+ return realtime;
834
+ };
835
+
836
+ var getParent = function (realtime, message) {
837
+ return message.parent = message.parent || realtime.messages[message.lastMsgHash];
838
+ };
839
+
840
+ var check = ChainPad.check = function(realtime) {
841
+ Common.assert(realtime.type === 'ChainPad');
842
+ Common.assert(typeof(realtime.authDoc) === 'string');
843
+
844
+ Patch.check(realtime.uncommitted, realtime.authDoc.length);
845
+
846
+ var uiDoc = Patch.apply(realtime.uncommitted, realtime.authDoc);
847
+ if (uiDoc.length !== realtime.uncommittedDocLength) {
848
+ Common.assert(0);
849
+ }
850
+ if (realtime.userInterfaceContent !== '') {
851
+ Common.assert(uiDoc === realtime.userInterfaceContent);
852
+ }
853
+
854
+ if (!Common.VALIDATE_ENTIRE_CHAIN_EACH_MSG) { return; }
855
+
856
+ var doc = realtime.authDoc;
857
+ var patchMsg = realtime.best;
858
+ Common.assert(patchMsg.content.inverseOf.parentHash === realtime.uncommitted.parentHash);
859
+ var patches = [];
860
+ do {
861
+ patches.push(patchMsg);
862
+ doc = Patch.apply(patchMsg.content.inverseOf, doc);
863
+ } while ((patchMsg = getParent(realtime, patchMsg)));
864
+ Common.assert(doc === '');
865
+ while ((patchMsg = patches.pop())) {
866
+ doc = Patch.apply(patchMsg.content, doc);
867
+ }
868
+ Common.assert(doc === realtime.authDoc);
869
+ };
870
+
871
+ var doOperation = ChainPad.doOperation = function (realtime, op) {
872
+ if (Common.PARANOIA) {
873
+ check(realtime);
874
+ realtime.userInterfaceContent = Operation.apply(op, realtime.userInterfaceContent);
875
+ }
876
+ Operation.check(op, realtime.uncommittedDocLength);
877
+ Patch.addOperation(realtime.uncommitted, op);
878
+ realtime.uncommittedDocLength += Operation.lengthChange(op);
879
+ };
880
+
881
+ var doPatch = ChainPad.doPatch = function (realtime, patch) {
882
+ if (Common.PARANOIA) {
883
+ check(realtime);
884
+ Common.assert(Patch.invert(realtime.uncommitted).parentHash === patch.parentHash);
885
+ realtime.userInterfaceContent = Patch.apply(patch, realtime.userInterfaceContent);
886
+ }
887
+ Patch.check(patch, realtime.uncommittedDocLength);
888
+ realtime.uncommitted = Patch.merge(realtime.uncommitted, patch);
889
+ realtime.uncommittedDocLength += Patch.lengthChange(patch);
890
+ };
891
+
892
+ var isAncestorOf = function (realtime, ancestor, decendent) {
893
+ if (!decendent || !ancestor) { return false; }
894
+ if (ancestor === decendent) { return true; }
895
+ return isAncestorOf(realtime, ancestor, getParent(realtime, decendent));
896
+ };
897
+
898
+ var parentCount = function (realtime, message) {
899
+ if (typeof(message.parentCount) !== 'number') {
900
+ message.parentCount = parentCount(realtime, getParent(realtime, message)) + 1;
901
+ }
902
+ return message.parentCount;
903
+ };
904
+
905
+ var applyPatch = function (realtime, isFromMe, patch) {
906
+ Common.assert(patch);
907
+ Common.assert(patch.inverseOf);
908
+ if (isFromMe) {
909
+ // Case 1: We're applying a patch which we originally created (yay our work was accepted)
910
+ // We will merge the inverse of the patch with our uncommitted work in order that
911
+ // we do not try to commit that work over again.
912
+ // Case 2: We're reverting a patch which had originally come from us, a.k.a. we're applying
913
+ // the inverse of that patch.
914
+ //
915
+ // In either scenario, we want to apply the inverse of the patch we are applying, to the
916
+ // uncommitted work. Whatever we "add" to the authDoc we "remove" from the uncommittedWork.
917
+ //
918
+ Common.assert(patch.parentHash === realtime.uncommitted.parentHash);
919
+ realtime.uncommitted = Patch.merge(patch.inverseOf, realtime.uncommitted);
920
+
921
+ } else {
922
+ // It's someone else's patch which was received, we need to *transform* out uncommitted
923
+ // work over their patch in order to preserve intent as much as possible.
924
+ realtime.uncommitted =
925
+ Patch.transform(
926
+ realtime.uncommitted, patch, realtime.authDoc, realtime.config.transformFunction);
927
+ }
928
+ realtime.uncommitted.parentHash = patch.inverseOf.parentHash;
929
+
930
+ realtime.authDoc = Patch.apply(patch, realtime.authDoc);
931
+
932
+ if (Common.PARANOIA) {
933
+ Common.assert(realtime.uncommitted.parentHash === patch.inverseOf.parentHash);
934
+ Common.assert(Sha.hex_sha256(realtime.authDoc) === realtime.uncommitted.parentHash);
935
+ realtime.userInterfaceContent = Patch.apply(realtime.uncommitted, realtime.authDoc);
936
+ }
937
+ };
938
+
939
+ var revertPatch = function (realtime, isFromMe, patch) {
940
+ applyPatch(realtime, isFromMe, patch.inverseOf);
941
+ };
942
+
943
+ var getBestChild = function (realtime, msg) {
944
+ var best = msg;
945
+ (realtime.messagesByParent[msg.hashOf] || []).forEach(function (child) {
946
+ Common.assert(child.lastMsgHash === msg.hashOf);
947
+ child = getBestChild(realtime, child);
948
+ if (parentCount(realtime, child) > parentCount(realtime, best)) { best = child; }
949
+ });
950
+ return best;
951
+ };
952
+
953
+ var pushUIPatch = function (realtime, patch) {
954
+ if (patch.operations.length) {
955
+ // push the uncommittedPatch out to the user interface.
956
+ for (var i = 0; i < realtime.patchHandlers.length; i++) {
957
+ realtime.patchHandlers[i](patch);
958
+ }
959
+ for (var i = 0; i < realtime.changeHandlers.length; i++) {
960
+ for (var j = patch.operations.length - 1; j >= 0; j--) {
961
+ var op = patch.operations[j];
962
+ realtime.changeHandlers[i](op.offset, op.toRemove, op.toInsert);
963
+ }
964
+ }
965
+ }
966
+ };
967
+
968
+ var validContent = function (realtime, contentGetter) {
969
+ if (!realtime.config.validateContent) { return true; }
970
+ try {
971
+ return realtime.config.validateContent(contentGetter());
972
+ } catch (e) {
973
+ warn(realtime, "Error in content validator [" + e.stack + "]");
974
+ }
975
+ return false;
976
+ };
977
+
978
+ var handleMessage = ChainPad.handleMessage = function (realtime, msgStr, isFromMe) {
979
+
980
+ if (Common.PARANOIA) { check(realtime); }
981
+ var msg = Message.fromString(msgStr);
982
+
983
+ if (msg.messageType !== Message.PATCH && msg.messageType !== Message.CHECKPOINT) {
984
+ debug(realtime, "unrecognized message type " + msg.messageType);
985
+ return;
986
+ }
987
+
988
+ msg.hashOf = Message.hashOf(msg);
989
+
990
+ if (Common.DEBUG) { debug(realtime, JSON.stringify([msg.hashOf, msg.content.operations])); }
991
+
992
+ if (realtime.messages[msg.hashOf]) {
993
+ if (realtime.setContentPatch && realtime.setContentPatch.hashOf === msg.hashOf) {
994
+ // We got the initial state patch, channel already has a pad, no need to send it.
995
+ realtime.setContentPatch = null;
996
+ } else {
997
+ debug(realtime, "Patch [" + msg.hashOf + "] is already known");
998
+ }
999
+ if (Common.PARANOIA) { check(realtime); }
1000
+ return;
1001
+ }
1002
+
1003
+ if (msg.content.isCheckpoint &&
1004
+ !validContent(realtime, function () { return msg.content.operations[0].toInsert }))
1005
+ {
1006
+ // If it's not a checkpoint, we verify it later on...
1007
+ debug(realtime, "Checkpoint [" + msg.hashOf + "] failed content validation");
1008
+ return;
1009
+ }
1010
+
1011
+ storeMessage(realtime, msg);
1012
+
1013
+ if (!isAncestorOf(realtime, realtime.rootMessage, msg)) {
1014
+ if (msg.content.isCheckpoint && realtime.best.isInitialMessage) {
1015
+ // We're starting with a trucated chain from a checkpoint, we will adopt this
1016
+ // as the root message and go with it...
1017
+ var userDoc = Patch.apply(realtime.uncommitted, realtime.authDoc);
1018
+ Common.assert(!Common.PARANOIA || realtime.userInterfaceContent === userDoc);
1019
+ var fixUserDocPatch = Patch.invert(realtime.uncommitted, realtime.authDoc);
1020
+ Patch.addOperation(fixUserDocPatch,
1021
+ Operation.create(0, realtime.authDoc.length, msg.content.operations[0].toInsert));
1022
+ fixUserDocPatch =
1023
+ Patch.simplify(fixUserDocPatch, userDoc, realtime.config.operationSimplify);
1024
+
1025
+ msg.parentCount = 0;
1026
+ realtime.rootMessage = realtime.best = msg;
1027
+
1028
+ realtime.authDoc = msg.content.operations[0].toInsert;
1029
+ realtime.uncommitted = Patch.create(Sha.hex_sha256(realtime.authDoc));
1030
+ realtime.uncommittedDocLength = realtime.authDoc.length;
1031
+ pushUIPatch(realtime, fixUserDocPatch);
1032
+
1033
+ if (Common.PARANOIA) { realtime.userInterfaceContent = realtime.authDoc; }
1034
+ return;
1035
+ } else {
1036
+ // we'll probably find the missing parent later.
1037
+ debug(realtime, "Patch [" + msg.hashOf + "] not connected to root");
1038
+ if (Common.PARANOIA) { check(realtime); }
1039
+ return;
1040
+ }
1041
+ }
1042
+
1043
+ // of this message fills in a hole in the chain which makes another patch better, swap to the
1044
+ // best child of this patch since longest chain always wins.
1045
+ msg = getBestChild(realtime, msg);
1046
+ msg.isFromMe = isFromMe;
1047
+ var patch = msg.content;
1048
+
1049
+ // Find the ancestor of this patch which is in the main chain, reverting as necessary
1050
+ var toRevert = [];
1051
+ var commonAncestor = realtime.best;
1052
+ if (!isAncestorOf(realtime, realtime.best, msg)) {
1053
+ var pcBest = parentCount(realtime, realtime.best);
1054
+ var pcMsg = parentCount(realtime, msg);
1055
+ if (pcBest < pcMsg
1056
+ || (pcBest === pcMsg
1057
+ && Common.strcmp(realtime.best.hashOf, msg.hashOf) > 0))
1058
+ {
1059
+ // switch chains
1060
+ while (commonAncestor && !isAncestorOf(realtime, commonAncestor, msg)) {
1061
+ toRevert.push(commonAncestor);
1062
+ commonAncestor = getParent(realtime, commonAncestor);
1063
+ }
1064
+ Common.assert(commonAncestor);
1065
+ debug(realtime, "Patch [" + msg.hashOf + "] better than best chain, switching");
1066
+ } else {
1067
+ debug(realtime, "Patch [" + msg.hashOf + "] chain is [" + pcMsg + "] best chain is [" +
1068
+ pcBest + "]");
1069
+ if (Common.PARANOIA) { check(realtime); }
1070
+ return;
1071
+ }
1072
+ }
1073
+
1074
+ // Find the parents of this patch which are not in the main chain.
1075
+ var toApply = [];
1076
+ var current = msg;
1077
+ do {
1078
+ toApply.unshift(current);
1079
+ current = getParent(realtime, current);
1080
+ Common.assert(current);
1081
+ } while (current !== commonAncestor);
1082
+
1083
+
1084
+ var authDocAtTimeOfPatch = realtime.authDoc;
1085
+
1086
+ for (var i = 0; i < toRevert.length; i++) {
1087
+ Common.assert(typeof(toRevert[i].content.inverseOf) !== 'undefined');
1088
+ authDocAtTimeOfPatch = Patch.apply(toRevert[i].content.inverseOf, authDocAtTimeOfPatch);
1089
+ }
1090
+
1091
+ // toApply.length-1 because we do not want to apply the new patch.
1092
+ for (var i = 0; i < toApply.length-1; i++) {
1093
+ if (typeof(toApply[i].content.inverseOf) === 'undefined') {
1094
+ toApply[i].content.inverseOf = Patch.invert(toApply[i].content, authDocAtTimeOfPatch);
1095
+ toApply[i].content.inverseOf.inverseOf = toApply[i].content;
1096
+ }
1097
+ authDocAtTimeOfPatch = Patch.apply(toApply[i].content, authDocAtTimeOfPatch);
1098
+ }
1099
+
1100
+ if (Sha.hex_sha256(authDocAtTimeOfPatch) !== patch.parentHash) {
1101
+ debug(realtime, "patch [" + msg.hashOf + "] parentHash is not valid");
1102
+ if (Common.PARANOIA) { check(realtime); }
1103
+ if (Common.TESTING) { throw new Error(); }
1104
+ forgetMessage(realtime, msg);
1105
+ return;
1106
+ }
1107
+
1108
+ if (patch.isCheckpoint) {
1109
+ // Ok, we have a checkpoint patch.
1110
+ // If the chain length is not equal to checkpointInterval then this patch is invalid.
1111
+ var i = 0;
1112
+ var checkpointP;
1113
+ for (var m = getParent(realtime, msg); m; m = getParent(realtime, m)) {
1114
+ if (m.content.isCheckpoint) {
1115
+ if (checkpointP) {
1116
+ checkpointP = m;
1117
+ break;
1118
+ }
1119
+ checkpointP = m;
1120
+ }
1121
+ }
1122
+ if (checkpointP && checkpointP !== realtime.rootMessage) {
1123
+ var point = parentCount(realtime, checkpointP);
1124
+ if (realtime.config.strictCheckpointValidation &&
1125
+ (point % realtime.config.checkpointInterval) !== 0)
1126
+ {
1127
+ debug(realtime, "checkpoint [" + msg.hashOf + "] at invalid point [" + point + "]");
1128
+ if (Common.PARANOIA) { check(realtime); }
1129
+ if (Common.TESTING) { throw new Error(); }
1130
+ forgetMessage(realtime, msg);
1131
+ return;
1132
+ }
1133
+
1134
+ // Time to prune some old messages from the chain
1135
+ debug(realtime, "checkpoint [" + msg.hashOf + "]");
1136
+ for (var m = getParent(realtime, checkpointP); m; m = getParent(realtime, m)) {
1137
+ debug(realtime, "pruning [" + m.hashOf + "]");
1138
+ forgetMessage(realtime, m);
1139
+ }
1140
+ realtime.rootMessage = checkpointP;
1141
+ }
1142
+ } else {
1143
+ var simplePatch =
1144
+ Patch.simplify(patch, authDocAtTimeOfPatch, realtime.config.operationSimplify);
1145
+ if (!Patch.equals(simplePatch, patch)) {
1146
+ debug(realtime, "patch [" + msg.hashOf + "] can be simplified");
1147
+ if (Common.PARANOIA) { check(realtime); }
1148
+ if (Common.TESTING) { throw new Error(); }
1149
+ forgetMessage(realtime, msg);
1150
+ return;
1151
+ }
1152
+
1153
+ if (!validContent(realtime,
1154
+ function () { return Patch.apply(patch, authDocAtTimeOfPatch); }))
1155
+ {
1156
+ debug(realtime, "Patch [" + msg.hashOf + "] failed content validation");
1157
+ return;
1158
+ }
1159
+ }
1160
+
1161
+ patch.inverseOf = Patch.invert(patch, authDocAtTimeOfPatch);
1162
+ patch.inverseOf.inverseOf = patch;
1163
+
1164
+ realtime.uncommitted = Patch.simplify(
1165
+ realtime.uncommitted, realtime.authDoc, realtime.config.operationSimplify);
1166
+ var oldUserInterfaceContent = Patch.apply(realtime.uncommitted, realtime.authDoc);
1167
+ if (Common.PARANOIA) {
1168
+ Common.assert(oldUserInterfaceContent === realtime.userInterfaceContent);
1169
+ }
1170
+
1171
+ // Derive the patch for the user's uncommitted work
1172
+ var uncommittedPatch = Patch.invert(realtime.uncommitted, realtime.authDoc);
1173
+
1174
+ for (var i = 0; i < toRevert.length; i++) {
1175
+ debug(realtime, "reverting [" + toRevert[i].hashOf + "]");
1176
+ if (toRevert[i].isFromMe) { debug(realtime, "reverting patch 'from me' [" + JSON.stringify(toRevert[i].content.operations) + "]"); }
1177
+ uncommittedPatch = Patch.merge(uncommittedPatch, toRevert[i].content.inverseOf);
1178
+ revertPatch(realtime, toRevert[i].isFromMe, toRevert[i].content);
1179
+ }
1180
+
1181
+ for (var i = 0; i < toApply.length; i++) {
1182
+ debug(realtime, "applying [" + toApply[i].hashOf + "]");
1183
+ uncommittedPatch = Patch.merge(uncommittedPatch, toApply[i].content);
1184
+ applyPatch(realtime, toApply[i].isFromMe, toApply[i].content);
1185
+ }
1186
+
1187
+ uncommittedPatch = Patch.merge(uncommittedPatch, realtime.uncommitted);
1188
+ uncommittedPatch = Patch.simplify(
1189
+ uncommittedPatch, oldUserInterfaceContent, realtime.config.operationSimplify);
1190
+
1191
+ realtime.uncommittedDocLength += Patch.lengthChange(uncommittedPatch);
1192
+ realtime.best = msg;
1193
+
1194
+ if (Common.PARANOIA) {
1195
+ // apply the uncommittedPatch to the userInterface content.
1196
+ var newUserInterfaceContent = Patch.apply(uncommittedPatch, oldUserInterfaceContent);
1197
+ Common.assert(realtime.userInterfaceContent.length === realtime.uncommittedDocLength);
1198
+ Common.assert(newUserInterfaceContent === realtime.userInterfaceContent);
1199
+ }
1200
+
1201
+ pushUIPatch(realtime, uncommittedPatch);
1202
+
1203
+ if (!uncommittedPatch.operations.length) {
1204
+ var onSettle = realtime.onSettle;
1205
+ realtime.onSettle = [];
1206
+ onSettle.forEach(function (handler) { handler(); });
1207
+ }
1208
+
1209
+ if (Common.PARANOIA) { check(realtime); }
1210
+ };
1211
+
1212
+ var getDepthOfState = function (content, minDepth, realtime) {
1213
+ Common.assert(typeof(content) === 'string');
1214
+
1215
+ // minimum depth is an optional argument which defaults to zero
1216
+ var minDepth = minDepth || 0;
1217
+
1218
+ if (minDepth === 0 && realtime.authDoc === content) {
1219
+ return 0;
1220
+ }
1221
+
1222
+ var hash = Sha.hex_sha256(content);
1223
+
1224
+ var patchMsg = realtime.best;
1225
+ var depth = 0;
1226
+
1227
+ do {
1228
+ if (depth < minDepth) {
1229
+ // you haven't exceeded the minimum depth
1230
+ } else {
1231
+ //console.log("Exceeded minimum depth");
1232
+ // you *have* exceeded the minimum depth
1233
+ if (patchMsg.content.parentHash === hash) {
1234
+ // you found it!
1235
+ return depth + 1;
1236
+ }
1237
+ }
1238
+ depth++;
1239
+ } while ((patchMsg = getParent(realtime, patchMsg)));
1240
+ return -1;
1241
+ };
1242
+
1243
+ module.exports.create = function (conf) {
1244
+ var realtime = ChainPad.create(conf);
1245
+ var out = {
1246
+ onPatch: enterChainPad(realtime, function (handler) {
1247
+ Common.assert(typeof(handler) === 'function');
1248
+ realtime.patchHandlers.push(handler);
1249
+ }),
1250
+ patch: enterChainPad(realtime, function (patch, x, y) {
1251
+ if (typeof(patch) === 'number') {
1252
+ // Actually they meant to call realtime.change()
1253
+ out.change(patch, x, y);
1254
+ return;
1255
+ }
1256
+ doPatch(realtime, patch);
1257
+ }),
1258
+
1259
+ onChange: enterChainPad(realtime, function (handler) {
1260
+ Common.assert(typeof(handler) === 'function');
1261
+ realtime.changeHandlers.push(handler);
1262
+ }),
1263
+ change: enterChainPad(realtime, function (offset, count, chars) {
1264
+ if (count === 0 && chars === '') { return; }
1265
+ doOperation(realtime, Operation.create(offset, count, chars));
1266
+ }),
1267
+
1268
+ onMessage: enterChainPad(realtime, function (handler) {
1269
+ Common.assert(typeof(handler) === 'function');
1270
+ realtime.messageHandlers.push(handler);
1271
+ }),
1272
+
1273
+ message: enterChainPad(realtime, function (message) {
1274
+ handleMessage(realtime, message, false);
1275
+ }),
1276
+
1277
+ start: enterChainPad(realtime, function () {
1278
+ if (realtime.syncSchedule) { unschedule(realtime, realtime.syncSchedule); }
1279
+ realtime.syncSchedule = schedule(realtime, function () { sync(realtime); });
1280
+ }),
1281
+
1282
+ abort: enterChainPad(realtime, function () {
1283
+ realtime.aborted = true;
1284
+ realtime.schedules.forEach(function (s) { clearTimeout(s) });
1285
+ }),
1286
+
1287
+ sync: enterChainPad(realtime, function () { sync(realtime); }),
1288
+
1289
+ getAuthDoc: function () { return realtime.authDoc; },
1290
+
1291
+ getUserDoc: function () { return Patch.apply(realtime.uncommitted, realtime.authDoc); },
1292
+
1293
+ getDepthOfState: function (content, minDepth) {
1294
+ return getDepthOfState(content, minDepth, realtime);
1295
+ },
1296
+
1297
+ onSettle: function (handler) {
1298
+ realtime.onSettle.push(handler);
1299
+ },
1300
+ };
1301
+ return out;
1302
+ };
1303
+
1304
+ },
1305
+ "Operation.js": function(module, exports, require){
1306
+ /*
1307
+ * Copyright 2014 XWiki SAS
1308
+ *
1309
+ * This program is free software: you can redistribute it and/or modify
1310
+ * it under the terms of the GNU Affero General Public License as published by
1311
+ * the Free Software Foundation, either version 3 of the License, or
1312
+ * (at your option) any later version.
1313
+ *
1314
+ * This program is distributed in the hope that it will be useful,
1315
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1316
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1317
+ * GNU Affero General Public License for more details.
1318
+ *
1319
+ * You should have received a copy of the GNU Affero General Public License
1320
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1321
+ */
1322
+ var Common = require('./Common');
1323
+
1324
+ var Operation = module.exports;
1325
+
1326
+ var check = Operation.check = function (op, docLength_opt) {
1327
+ Common.assert(op.type === 'Operation');
1328
+ Common.assert(Common.isUint(op.offset));
1329
+ Common.assert(Common.isUint(op.toRemove));
1330
+ Common.assert(typeof(op.toInsert) === 'string');
1331
+ Common.assert(op.toRemove > 0 || op.toInsert.length > 0);
1332
+ Common.assert(typeof(docLength_opt) !== 'number' || op.offset + op.toRemove <= docLength_opt);
1333
+ };
1334
+
1335
+ var create = Operation.create = function (offset, toRemove, toInsert) {
1336
+ var out = {
1337
+ type: 'Operation',
1338
+ offset: offset || 0,
1339
+ toRemove: toRemove || 0,
1340
+ toInsert: toInsert || '',
1341
+ };
1342
+ if (Common.PARANOIA) { check(out); }
1343
+ return out;
1344
+ };
1345
+
1346
+ var toObj = Operation.toObj = function (op) {
1347
+ if (Common.PARANOIA) { check(op); }
1348
+ return [op.offset,op.toRemove,op.toInsert];
1349
+ };
1350
+
1351
+ var fromObj = Operation.fromObj = function (obj) {
1352
+ Common.assert(Array.isArray(obj) && obj.length === 3);
1353
+ return create(obj[0], obj[1], obj[2]);
1354
+ };
1355
+
1356
+ var clone = Operation.clone = function (op) {
1357
+ return create(op.offset, op.toRemove, op.toInsert);
1358
+ };
1359
+
1360
+ /**
1361
+ * @param op the operation to apply.
1362
+ * @param doc the content to apply the operation on
1363
+ */
1364
+ var apply = Operation.apply = function (op, doc)
1365
+ {
1366
+ if (Common.PARANOIA) {
1367
+ check(op);
1368
+ Common.assert(typeof(doc) === 'string');
1369
+ Common.assert(op.offset + op.toRemove <= doc.length);
1370
+ }
1371
+ return doc.substring(0,op.offset) + op.toInsert + doc.substring(op.offset + op.toRemove);
1372
+ };
1373
+
1374
+ var invert = Operation.invert = function (op, doc) {
1375
+ if (Common.PARANOIA) {
1376
+ check(op);
1377
+ Common.assert(typeof(doc) === 'string');
1378
+ Common.assert(op.offset + op.toRemove <= doc.length);
1379
+ }
1380
+ var rop = clone(op);
1381
+ rop.toInsert = doc.substring(op.offset, op.offset + op.toRemove);
1382
+ rop.toRemove = op.toInsert.length;
1383
+ return rop;
1384
+ };
1385
+
1386
+ var simplify = Operation.simplify = function (op, doc) {
1387
+ if (Common.PARANOIA) {
1388
+ check(op);
1389
+ Common.assert(typeof(doc) === 'string');
1390
+ Common.assert(op.offset + op.toRemove <= doc.length);
1391
+ }
1392
+ var rop = invert(op, doc);
1393
+ op = clone(op);
1394
+
1395
+ var minLen = Math.min(op.toInsert.length, rop.toInsert.length);
1396
+ var i;
1397
+ for (i = 0; i < minLen && rop.toInsert[i] === op.toInsert[i]; i++) ;
1398
+ op.offset += i;
1399
+ op.toRemove -= i;
1400
+ op.toInsert = op.toInsert.substring(i);
1401
+ rop.toInsert = rop.toInsert.substring(i);
1402
+
1403
+ if (rop.toInsert.length === op.toInsert.length) {
1404
+ for (i = rop.toInsert.length-1; i >= 0 && rop.toInsert[i] === op.toInsert[i]; i--) ;
1405
+ op.toInsert = op.toInsert.substring(0, i+1);
1406
+ op.toRemove = i+1;
1407
+ }
1408
+
1409
+ if (op.toRemove === 0 && op.toInsert.length === 0) { return null; }
1410
+ return op;
1411
+ };
1412
+
1413
+ var equals = Operation.equals = function (opA, opB) {
1414
+ return (opA.toRemove === opB.toRemove
1415
+ && opA.toInsert === opB.toInsert
1416
+ && opA.offset === opB.offset);
1417
+ };
1418
+
1419
+ var lengthChange = Operation.lengthChange = function (op)
1420
+ {
1421
+ if (Common.PARANOIA) { check(op); }
1422
+ return op.toInsert.length - op.toRemove;
1423
+ };
1424
+
1425
+ /*
1426
+ * @return the merged operation OR null if the result of the merger is a noop.
1427
+ */
1428
+ var merge = Operation.merge = function (oldOpOrig, newOpOrig) {
1429
+ if (Common.PARANOIA) {
1430
+ check(newOpOrig);
1431
+ check(oldOpOrig);
1432
+ }
1433
+
1434
+ var newOp = clone(newOpOrig);
1435
+ var oldOp = clone(oldOpOrig);
1436
+ var offsetDiff = newOp.offset - oldOp.offset;
1437
+
1438
+ if (newOp.toRemove > 0) {
1439
+ var origOldInsert = oldOp.toInsert;
1440
+ oldOp.toInsert = (
1441
+ oldOp.toInsert.substring(0,offsetDiff)
1442
+ + oldOp.toInsert.substring(offsetDiff + newOp.toRemove)
1443
+ );
1444
+ newOp.toRemove -= (origOldInsert.length - oldOp.toInsert.length);
1445
+ if (newOp.toRemove < 0) { newOp.toRemove = 0; }
1446
+
1447
+ oldOp.toRemove += newOp.toRemove;
1448
+ newOp.toRemove = 0;
1449
+ }
1450
+
1451
+ if (offsetDiff < 0) {
1452
+ oldOp.offset += offsetDiff;
1453
+ oldOp.toInsert = newOp.toInsert + oldOp.toInsert;
1454
+
1455
+ } else if (oldOp.toInsert.length === offsetDiff) {
1456
+ oldOp.toInsert = oldOp.toInsert + newOp.toInsert;
1457
+
1458
+ } else if (oldOp.toInsert.length > offsetDiff) {
1459
+ oldOp.toInsert = (
1460
+ oldOp.toInsert.substring(0,offsetDiff)
1461
+ + newOp.toInsert
1462
+ + oldOp.toInsert.substring(offsetDiff)
1463
+ );
1464
+ } else {
1465
+ throw new Error("should never happen\n" +
1466
+ JSON.stringify([oldOpOrig,newOpOrig], null, ' '));
1467
+ }
1468
+
1469
+ if (oldOp.toInsert === '' && oldOp.toRemove === 0) {
1470
+ return null;
1471
+ }
1472
+ if (Common.PARANOIA) { check(oldOp); }
1473
+
1474
+ return oldOp;
1475
+ };
1476
+
1477
+ /**
1478
+ * If the new operation deletes what the old op inserted or inserts content in the middle of
1479
+ * the old op's content or if they abbut one another, they should be merged.
1480
+ */
1481
+ var shouldMerge = Operation.shouldMerge = function (oldOp, newOp) {
1482
+ if (Common.PARANOIA) {
1483
+ check(oldOp);
1484
+ check(newOp);
1485
+ }
1486
+ if (newOp.offset < oldOp.offset) {
1487
+ return (oldOp.offset <= (newOp.offset + newOp.toRemove));
1488
+ } else {
1489
+ return (newOp.offset <= (oldOp.offset + oldOp.toInsert.length));
1490
+ }
1491
+ };
1492
+
1493
+ /**
1494
+ * Rebase newOp against oldOp.
1495
+ *
1496
+ * @param oldOp the eariler operation to have happened.
1497
+ * @param newOp the later operation to have happened (in time).
1498
+ * @return either the untouched newOp if it need not be rebased,
1499
+ * the rebased clone of newOp if it needs rebasing, or
1500
+ * null if newOp and oldOp must be merged.
1501
+ */
1502
+ var rebase = Operation.rebase = function (oldOp, newOp) {
1503
+ if (Common.PARANOIA) {
1504
+ check(oldOp);
1505
+ check(newOp);
1506
+ }
1507
+ if (newOp.offset < oldOp.offset) { return newOp; }
1508
+ newOp = clone(newOp);
1509
+ newOp.offset += oldOp.toRemove;
1510
+ newOp.offset -= oldOp.toInsert.length;
1511
+ return newOp;
1512
+ };
1513
+
1514
+ /**
1515
+ * this is a lossy and dirty algorithm, everything else is nice but transformation
1516
+ * has to be lossy because both operations have the same base and they diverge.
1517
+ * This could be made nicer and/or tailored to a specific data type.
1518
+ *
1519
+ * @param toTransform the operation which is converted *MUTATED*.
1520
+ * @param transformBy an existing operation which also has the same base.
1521
+ * @return toTransform *or* null if the result is a no-op.
1522
+ */
1523
+
1524
+ var transform0 = Operation.transform0 = function (text, toTransformOrig, transformByOrig) {
1525
+ // Cloning the original transformations makes this algorithm such that it
1526
+ // **DOES NOT MUTATE ANYMORE**
1527
+ var toTransform = Operation.clone(toTransformOrig);
1528
+ var transformBy = Operation.clone(transformByOrig);
1529
+
1530
+ if (toTransform.offset > transformBy.offset) {
1531
+ if (toTransform.offset > transformBy.offset + transformBy.toRemove) {
1532
+ // simple rebase
1533
+ toTransform.offset -= transformBy.toRemove;
1534
+ toTransform.offset += transformBy.toInsert.length;
1535
+ return toTransform;
1536
+ }
1537
+ // goto the end, anything you deleted that they also deleted should be skipped.
1538
+ var newOffset = transformBy.offset + transformBy.toInsert.length;
1539
+ toTransform.toRemove = 0; //-= (newOffset - toTransform.offset);
1540
+ if (toTransform.toRemove < 0) { toTransform.toRemove = 0; }
1541
+ toTransform.offset = newOffset;
1542
+ if (toTransform.toInsert.length === 0 && toTransform.toRemove === 0) {
1543
+ return null;
1544
+ }
1545
+ return toTransform;
1546
+ }
1547
+ if (toTransform.offset + toTransform.toRemove < transformBy.offset) {
1548
+ return toTransform;
1549
+ }
1550
+ toTransform.toRemove = transformBy.offset - toTransform.offset;
1551
+ if (toTransform.toInsert.length === 0 && toTransform.toRemove === 0) {
1552
+ return null;
1553
+ }
1554
+ return toTransform;
1555
+ };
1556
+
1557
+ /**
1558
+ * @param toTransform the operation which is converted
1559
+ * @param transformBy an existing operation which also has the same base.
1560
+ * @return a modified clone of toTransform *or* toTransform itself if no change was made.
1561
+ */
1562
+ var transform = Operation.transform = function (text, toTransform, transformBy, transformFunction) {
1563
+ if (Common.PARANOIA) {
1564
+ check(toTransform);
1565
+ check(transformBy);
1566
+ }
1567
+ transformFunction = transformFunction || transform0;
1568
+ toTransform = clone(toTransform);
1569
+ var result = transformFunction(text, toTransform, transformBy);
1570
+ if (Common.PARANOIA && result) { check(result); }
1571
+ return result;
1572
+ };
1573
+
1574
+ /** Used for testing. */
1575
+ var random = Operation.random = function (docLength) {
1576
+ Common.assert(Common.isUint(docLength));
1577
+ var offset = Math.floor(Math.random() * 100000000 % docLength) || 0;
1578
+ var toRemove = Math.floor(Math.random() * 100000000 % (docLength - offset)) || 0;
1579
+ var toInsert = '';
1580
+ do {
1581
+ var toInsert = Common.randomASCII(Math.floor(Math.random() * 20));
1582
+ } while (toRemove === 0 && toInsert === '');
1583
+ return create(offset, toRemove, toInsert);
1584
+ };
1585
+
1586
+ }
1587
+ };
1588
+ ChainPad = r("ChainPad.js");}());