Socket
Socket
Sign inDemoInstall

chainpad

Package Overview
Dependencies
29
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.4.2 to 5.2.0

client/Diff_test.js

4

client/AllTests.js

@@ -25,3 +25,3 @@ /* globals document */

test.main(cycles, waitFor());
}).nThen(function (waitFor) {
}).nThen(function () {
console.log("\n\nCompleted Test " + file + "\n\n");

@@ -32,5 +32,5 @@ }).nThen(waitFor());

nt(function (waitFor) {
nt(function () {
console.log('Done');
console.log('in ' + (new Date().getTime() - timeOne));
});

@@ -47,2 +47,3 @@ /*@flow*/

/*
var insert = function (doc, offset, chars) {

@@ -54,3 +55,3 @@ return doc.substring(0,offset) + chars + doc.substring(offset);

return doc.substring(0,offset) + doc.substring(offset+count);
};
};*/

@@ -277,3 +278,3 @@ var registerNode = function (name, initialDoc, conf) {

var i = 0;
var oldUserDoc;
//var oldUserDoc;
var oldAuthDoc;

@@ -454,3 +455,3 @@ var to = xsetInterval(function () {

var i = 0;
var oldUserDoc;
//var oldUserDoc;
var oldAuthBlock;

@@ -514,3 +515,3 @@ var oldAuthDoc;

var i = 0;
var oldUserDoc;
//var oldUserDoc;
var to = xsetInterval(function () {

@@ -553,4 +554,44 @@ // on the 100th random change, check if getting the state at oldAuthBlock works...

var main = module.exports.main = function (cycles /*:number*/, callback /*:()=>void*/) {
/* Insert an emoji in the document, then replace it by another emoji;
* Check that the resulting patch is not containing a broken half-emoji
* by trying to "encodeURIComonent" it.
*/
var emojiTest = function (callback) {
var rt = ChainPad.create({
userName: 'x',
initialState: ''
});
rt.start();
// Check if the pacthes are encryptable
rt.onMessage(function (message, cb) {
console.log(message);
try {
encodeURIComponent(message);
} catch (e) {
console.log('Error');
console.log(e.message);
Common.assert(false);
}
setTimeout(cb);
});
nThen(function (waitFor) {
// Insert first emoji in the userdoc
var emoji1 = "\uD83D\uDE00";
rt.contentUpdate(emoji1);
rt.onSettle(waitFor());
}).nThen(function (waitFor) {
// Replace the emoji by a different one
var emoji2 = "\uD83D\uDE11";
rt.contentUpdate(emoji2);
rt.onSettle(waitFor());
}).nThen(function () {
rt.abort();
callback();
});
};
module.exports.main = function (cycles /*:number*/, callback /*:()=>void*/) {
nThen(function (waitFor) {
startup(waitFor());

@@ -573,3 +614,5 @@ }).nThen(function (waitFor) {

benchmarkSync(waitFor());
}).nThen(function (waitFor) {
emojiTest(waitFor());
}).nThen(callback);
};

@@ -24,3 +24,8 @@ /*@flow*/

var Sha = module.exports.Sha = require('./sha256');
var Diff = module.exports.Diff = require('./Diff');
var TextTransformer = module.exports.TextTransformer = require('./transform/TextTransformer');
module.exports.NaiveJSONTransformer = require('./transform/NaiveJSONTransformer');
module.exports.SmartJSONTransformer = require('./transform/SmartJSONTransformer');
// hex_sha256('')

@@ -43,3 +48,3 @@ var EMPTY_STR_HASH = module.exports.EMPTY_STR_HASH =

var debug = function (realtime, msg) {
if (realtime.logLevel > 0) {
if (realtime.logLevel > 1) {
console.log("[" + realtime.userName + "] " + msg);

@@ -76,3 +81,3 @@ }

var onMessage = function (realtime, message, callback) {
var onMessage = function (realtime, message, callback /*:(?string)=>void*/) {
if (!realtime.messageHandlers.length) {

@@ -89,8 +94,8 @@ callback("no onMessage() handler registered");

} catch (e) {
callback(e);
callback(e.stack);
}
};
var sendMessage = function (realtime, msg, callback) {
var strMsg = Message.toString(msg);
var sendMessage = function (realtime, msg, callback, timeSent) {
var strMsg = Message.toStr(msg);

@@ -106,3 +111,8 @@ onMessage(realtime, strMsg, function (err) {

Common.assert(pending.hash === msg.hashOf);
handleMessage(realtime, strMsg, true);
if (handleMessage(realtime, strMsg, true)) {
realtime.timeOfLastSuccess = +new Date();
realtime.lag = +new Date() - pending.timeSent;
} else {
debug(realtime, "Our message [" + msg.hashOf + "] failed validation");
}
pending.callback();

@@ -112,12 +122,25 @@ }

/*
var timeout = schedule(realtime, function () {
debug(realtime, "Failed to send message [" + msg.hashOf + "] to server");
sync(realtime);
var pending = realtime.pending;
if (pending) {
//var timeSent = pending.timeSent;
realtime.pending = null;
realtime.syncSchedule = -1;
}
sync(realtime, 0);
if (!pending) {
throw new Error("INTERNAL ERROR: Message timed out but no realtime.pending");
}
}, 10000 + (Math.random() * 5000));
*/
if (realtime.pending) { throw new Error("there is already a pending message"); }
if (realtime.timeOfLastSuccess === -1) { realtime.timeOfLastSuccess = +new Date(); }
realtime.pending = {
hash: msg.hashOf,
timeSent: timeSent || +new Date(),
callback: function () {
unschedule(realtime, timeout);
//unschedule(realtime, timeout);
realtime.syncSchedule = schedule(realtime, function () { sync(realtime); }, 0);

@@ -147,3 +170,3 @@ callback();

var sync = function (realtime) {
var sync = function (realtime, timeSent) {
if (Common.PARANOIA) { check(realtime); }

@@ -165,2 +188,3 @@ if (realtime.syncSchedule && !realtime.pending) {

settle(realtime);
realtime.timeOfLastSuccess = +new Date();
realtime.syncSchedule = schedule(realtime, function () { sync(realtime); });

@@ -183,3 +207,3 @@ return;

debug(realtime, "Checkpoint sent and accepted");
});
}, timeSent);
return;

@@ -201,3 +225,3 @@ }

}
});
}, timeSent);
};

@@ -262,4 +286,2 @@

uncommittedDocLength: config.initialState.length,
patchHandlers: [],

@@ -273,3 +295,3 @@ changeHandlers: [],

syncSchedule: -1,
syncSchedule: -2,

@@ -296,3 +318,6 @@ // this is only used if PARANOIA is enabled.

best: best
best: best,
lag: 0,
timeOfLastSuccess: -1,
};

@@ -318,3 +343,3 @@ storeMessage(realtime, zeroMsg);

var uiDoc = Patch.apply(realtime.uncommitted, realtime.authDoc);
Common.assert(uiDoc.length === realtime.uncommittedDocLength);
Common.assert(uiDoc.length === uncommittedDocLength(realtime));
if (realtime.userInterfaceContent !== '') {

@@ -343,2 +368,6 @@ Common.assert(uiDoc === realtime.userInterfaceContent);

var uncommittedDocLength = function (realtime) {
return realtime.authDoc.length + Patch.lengthChange(realtime.uncommitted);
};
var doOperation = function (realtime, op) {

@@ -349,5 +378,4 @@ if (Common.PARANOIA) {

}
Operation.check(op, realtime.uncommittedDocLength);
Operation.check(op, uncommittedDocLength(realtime));
Patch.addOperation(realtime.uncommitted, op);
realtime.uncommittedDocLength += Operation.lengthChange(op);
};

@@ -362,5 +390,4 @@

}
Patch.check(patch, realtime.uncommittedDocLength);
Patch.check(patch, uncommittedDocLength(realtime));
realtime.uncommitted = Patch.merge(realtime.uncommitted, patch);
realtime.uncommittedDocLength += Patch.lengthChange(patch);
};

@@ -398,2 +425,3 @@

Common.assert(patch);
var newAuthDoc;
if (isFromMe) {

@@ -415,9 +443,24 @@ // Case 1: We're applying a patch which we originally created (yay our work was accepted)

// work over their patch in order to preserve intent as much as possible.
realtime.uncommitted =
Patch.transform(
realtime.uncommitted, patch, realtime.authDoc, realtime.config.transformFunction);
//debug(realtime, "Transforming patch " + JSON.stringify(realtime.uncommitted.operations));
realtime.uncommitted = Patch.transform(
realtime.uncommitted,
patch,
realtime.authDoc,
realtime.config.patchTransformer
);
//debug(realtime, "By " + JSON.stringify(patch.operations) +
// "\nResult " + JSON.stringify(realtime.uncommitted.operations));
if (realtime.config.validateContent) {
newAuthDoc = Patch.apply(patch, realtime.authDoc);
var userDoc = Patch.apply(realtime.uncommitted, newAuthDoc);
if (!realtime.config.validateContent(userDoc)) {
warn(realtime, "Transformed patch is not valid");
// big hammer
realtime.uncommitted = Patch.create(Sha.hex_sha256(realtime.authDoc));
}
}
}
Common.assert(realtime.uncommitted.parentHash === inversePatch(patch).parentHash);
realtime.authDoc = Patch.apply(patch, realtime.authDoc);
realtime.authDoc = newAuthDoc || Patch.apply(patch, realtime.authDoc);

@@ -482,3 +525,3 @@ if (Common.PARANOIA) {

if (Common.DEBUG) { debug(realtime, JSON.stringify([msg.hashOf, msg.content.operations])); }
debug(realtime, JSON.stringify([msg.hashOf, msg.content.operations]));

@@ -490,2 +533,8 @@ if (realtime.messages[msg.hashOf]) {

} else {
if (msg.content.isCheckpoint) {
debug(realtime, "[" +
(isFromMe ? "our" : "their") +
"] Checkpoint [" + msg.hashOf + "] is already known");
return true;
}
debug(realtime, "Patch [" + msg.hashOf + "] is already known");

@@ -525,7 +574,6 @@ }

realtime.uncommitted = Patch.create(Sha.hex_sha256(realtime.authDoc));
realtime.uncommittedDocLength = realtime.authDoc.length;
pushUIPatch(realtime, fixUserDocPatch);
if (Common.PARANOIA) { realtime.userInterfaceContent = realtime.authDoc; }
return;
return true;
} else {

@@ -567,3 +615,3 @@ // we'll probably find the missing parent later.

if (Common.PARANOIA) { check(realtime); }
return;
return true;
}

@@ -701,3 +749,2 @@ }

realtime.uncommittedDocLength += Patch.lengthChange(uncommittedPatch);
realtime.best = msg;

@@ -708,3 +755,3 @@

var newUserInterfaceContent = Patch.apply(uncommittedPatch, oldUserInterfaceContent);
Common.assert(realtime.userInterfaceContent.length === realtime.uncommittedDocLength);
Common.assert(realtime.userInterfaceContent.length === uncommittedDocLength(realtime));
Common.assert(newUserInterfaceContent === realtime.userInterfaceContent);

@@ -720,2 +767,4 @@ }

if (Common.PARANOIA) { check(realtime); }
return true;
};

@@ -774,3 +823,24 @@

var wrapMessage = function (realtime, msg) {
/*::
export type ChainPad_BlockContent_t = {
error: ?string,
doc: ?string
};
export type ChainPad_Block_t = {
type: 'Block',
hashOf: string,
lastMsgHash: string,
isCheckpoint: boolean,
getParent: ()=>?ChainPad_Block_t,
getContent: ()=>{
error: ?string,
doc: ?string
},
getPatch: ()=>Patch_t,
getInversePatch: ()=>Patch_t,
equals: (?ChainPad_Block_t, ?any)=>boolean
};
*/
var wrapMessage = function (realtime, msg) /*:ChainPad_Block_t*/ {
return Object.freeze({

@@ -790,4 +860,4 @@ type: 'Block',

if (msgOpt) { return msg === msgOpt; }
Common.assert(block.type === 'Block');
return block.equals(null, msg);
if (!block || typeof(block) !== 'object' || block.type !== 'Block') { return false; }
return block.equals(block, msg);
}

@@ -799,2 +869,5 @@ });

config = config || {};
if (config.transformFunction) {
throw new Error("chainpad config transformFunction is nolonger used");
}
return Object.freeze({

@@ -807,7 +880,11 @@ initialState: config.initialState || '',

operationSimplify: config.operationSimplify || Operation.simplify,
logLevel: (typeof(config.logLevel) === 'number') ? config.logLevel : 1,
logLevel: (typeof(config.logLevel) === 'number') ? config.logLevel : 2,
noPrune: config.noPrune,
transformFunction: config.transformFunction || Operation.transform0,
patchTransformer: config.patchTransformer || TextTransformer,
userName: config.userName || 'anonymous',
validateContent: config.validateContent || function () { return true; }
validateContent: config.validateContent || function (x) { x = x; return true; },
diffFunction: config.diffFunction ||
function (strA, strB /*:string*/) {
return Diff.diff(strA, strB /*, config.diffBlockSize */);
},
});

@@ -819,3 +896,5 @@ };

import type { Operation_Simplify_t } from './Operation';
import type { Operation_t } from './Operation';
import type { Patch_t } from './Patch';
import type { Patch_Transform_t } from './Patch';
export type ChainPad_Config_t = {

@@ -825,9 +904,11 @@ initialState?: string,

avgSyncMilliseconds?: number,
validateContent?: (string)=>boolean,
strictCheckpointValidation?: boolean,
patchTransformer?: Patch_Transform_t,
operationSimplify?: Operation_Simplify_t,
logLevel?: number,
transformFunction?: Operation_Transform_t,
userName?: string,
validateContent?: (string)=>boolean,
noPrune?: boolean
noPrune?: boolean,
diffFunction?: (string, string)=>Array<Operation_t>,
diffBlockSize?: number
};

@@ -862,2 +943,9 @@ */

contentUpdate: function (newContent /*:string*/) {
var ops = realtime.config.diffFunction(realtime.authDoc, newContent);
var uncommitted = Patch.create(realtime.uncommitted.parentHash);
Array.prototype.push.apply(uncommitted.operations, ops);
realtime.uncommitted = uncommitted;
},
onMessage: function (handler /*:(string, ()=>void)=>void*/) {

@@ -873,3 +961,5 @@ Common.assert(typeof(handler) === 'function');

start: function () {
realtime.aborted = false;
if (realtime.syncSchedule) { unschedule(realtime, realtime.syncSchedule); }
realtime.pending = null;
realtime.syncSchedule = schedule(realtime, function () { sync(realtime); });

@@ -910,7 +1000,16 @@ },

getLag: function () {
var isPending = !!realtime.pending;
var lag = realtime.lag;
if (realtime.pending) { lag = +new Date() - realtime.timeOfLastSuccess; }
return {
pending: isPending,
lag: lag,
active: (!realtime.aborted && realtime.syncSchedule !== -2)
};
},
_: undefined
};
if (Common.DEBUG) {
out._ = realtime;
}
out._ = realtime;
return Object.freeze(out);

@@ -917,0 +1016,0 @@ };

/*@flow*/
/* globals localStorage */
/* globals localStorage, window */
/*

@@ -20,21 +20,31 @@ * Copyright 2014 XWiki SAS

"use strict";
var DEBUG = module.exports.DEBUG =
(typeof(localStorage) !== 'undefined' && localStorage['ChainPad_DEBUG']);
var PARANOIA = module.exports.PARANOIA =
(typeof(localStorage) !== 'undefined' && localStorage['ChainPad_PARANOIA']);
module.exports.global = (function () {
if (typeof(self) !== 'undefined') { return self; }
if (typeof(global) !== 'undefined') { return global; }
if (typeof(window) !== 'undefined') { return window; }
throw new Error("no self, nor global, nor window");
}());
var cfg = function (name) {
if (typeof(localStorage) !== 'undefined' && localStorage[name]) {
return localStorage[name];
}
// flow thinks global may be undefined
return module.exports.global[name];
};
var PARANOIA = module.exports.PARANOIA = cfg("ChainPad_PARANOIA");
/* Good testing but slooooooooooow */
var VALIDATE_ENTIRE_CHAIN_EACH_MSG = module.exports.VALIDATE_ENTIRE_CHAIN_EACH_MSG =
(typeof(localStorage) !== 'undefined' && localStorage['ChainPad_VALIDATE_ENTIRE_CHAIN_EACH_MSG']);
module.exports.VALIDATE_ENTIRE_CHAIN_EACH_MSG = cfg("ChainPad_VALIDATE_ENTIRE_CHAIN_EACH_MSG");
/* throw errors over non-compliant messages which would otherwise be treated as invalid */
var TESTING = module.exports.TESTING =
(typeof(localStorage) !== 'undefined' && localStorage['ChainPad_TESTING']);
module.exports.TESTING = cfg("ChainPad_TESTING");
var assert = module.exports.assert = function (expr /*:any*/) {
module.exports.assert = function (expr /*:any*/) {
if (!expr) { throw new Error("Failed assertion"); }
};
var isUint = module.exports.isUint = function (integer /*:number*/) {
module.exports.isUint = function (integer /*:number*/) {
return (typeof(integer) === 'number') &&

@@ -45,3 +55,3 @@ (Math.floor(integer) === integer) &&

var randomASCII = module.exports.randomASCII = function (length /*:number*/) {
module.exports.randomASCII = function (length /*:number*/) {
var content = [];

@@ -54,3 +64,3 @@ for (var i = 0; i < length; i++) {

var strcmp = module.exports.strcmp = function (a /*:string*/, b /*:string*/) {
module.exports.strcmp = function (a /*:string*/, b /*:string*/) {
if (PARANOIA && typeof(a) !== 'string') { throw new Error(); }

@@ -57,0 +67,0 @@ if (PARANOIA && typeof(b) !== 'string') { throw new Error(); }

@@ -20,3 +20,3 @@ /*@flow*/

var Common = require('./Common');
var Operation = require('./Operation');
//var Operation = require('./Operation');
var Patch = require('./Patch');

@@ -83,3 +83,3 @@ var Sha = require('./sha256');

// $FlowFixMe doesn't like the toString()
var toString = Message.toString = function (msg /*:Message_t*/) {
var toString = Message.toStr = Message.toString = function (msg /*:Message_t*/) {
if (Common.PARANOIA) { check(msg); }

@@ -94,3 +94,3 @@ if (msg.messageType === PATCH || msg.messageType === CHECKPOINT) {

var fromString = Message.fromString = function (str /*:string*/) /*:Message_t*/ {
Message.fromString = function (str /*:string*/) /*:Message_t*/ {
var m = JSON.parse(str);

@@ -97,0 +97,0 @@ if (m[0] !== CHECKPOINT && m[0] !== PATCH) { throw new Error("invalid message type " + m[0]); }

@@ -36,3 +36,3 @@ /*@flow*/

if (rOperations[i]) {
var inverse = Operation.invert(rOperations[i], docx);
//var inverse = Operation.invert(rOperations[i], docx);
docx = Operation.apply(rOperations[i], docx);

@@ -117,3 +117,18 @@ }

var main = module.exports.main = function (cycles /*:number*/, callback /*:()=>void*/) {
var emoji = function(callback) {
var oldEmoji = "abc\uD83D\uDE00def";
var newEmoji = "abc\uD83D\uDE11def";
var op = Operation.create(3, 2, newEmoji);
var sop = Operation.simplify(op, oldEmoji);
Common.assert(sop !== null);
if (sop !== null)
{
Common.assert(op.toRemove === sop.toRemove);
}
callback();
};
module.exports.main = function (cycles /*:number*/, callback /*:()=>void*/) {
nThen(function (waitFor) {

@@ -127,3 +142,5 @@ simplify(cycles, waitFor());

merge(cycles, waitFor());
}).nThen(function (waitFor) {
emoji(waitFor());
}).nThen(callback);
};

@@ -60,3 +60,3 @@ /*@flow*/

var toObj = Operation.toObj = function (op /*:Operation_t*/) {
Operation.toObj = function (op /*:Operation_t*/) {
if (Common.PARANOIA) { check(op); }

@@ -67,3 +67,3 @@ return [op.offset,op.toRemove,op.toInsert];

// Allow any as input because we assert its type internally..
var fromObj = Operation.fromObj = function (obj /*:any*/) {
Operation.fromObj = function (obj /*:any*/) {
Common.assert(Array.isArray(obj) && obj.length === 3);

@@ -86,2 +86,8 @@ return create(obj[0], obj[1], obj[2]);

Operation.applyMulti = function (ops /*:Array<Operation_t>*/, doc /*:string*/)
{
for (var i = ops.length - 1; i >= 0; i--) { doc = apply(ops[i], doc); }
return doc;
};
var invert = Operation.invert = function (op /*:Operation_t*/, doc /*:string*/) {

@@ -100,3 +106,17 @@ if (Common.PARANOIA) {

var simplify = Operation.simplify = function (op /*:Operation_t*/, doc /*:string*/) {
// see http://unicode.org/faq/utf_bom.html#utf16-7
var surrogatePattern = /[\uD800-\uDBFF]|[\uDC00-\uDFFF]/;
var hasSurrogate = Operation.hasSurrogate = function(str /*:string*/) {
return surrogatePattern.test(str);
};
/**
* ATTENTION: This function is not just a neat way to make patches smaller, it's
* actually part of the ChainPad consensus rules, so if you have a clever
* idea to make it a bit faster, it is going to cause ChainPad to reject
* old patches, which means when you go to load the history of a pad, you're
* sunk.
* tl;dr can't touch this
*/
Operation.simplify = function (op /*:Operation_t*/, doc /*:string*/) {
if (Common.PARANOIA) {

@@ -110,4 +130,13 @@ check(op);

var minLen = Math.min(op.toInsert.length, rop.toInsert.length);
var i;
for (i = 0; i < minLen && rop.toInsert[i] === op.toInsert[i]; i++) ;
var i = 0;
while (i < minLen && rop.toInsert[i] === op.toInsert[i]) {
if (hasSurrogate(rop.toInsert[i]) || hasSurrogate(op.toInsert[i])) {
if (op.toInsert[i + 1] === rop.toInsert[i + 1]) {
i++;
} else {
break;
}
}
i++;
}
var opOffset = op.offset + i;

@@ -128,3 +157,3 @@ var opToRemove = op.toRemove - i;

var equals = Operation.equals = function (opA /*:Operation_t*/, opB /*:Operation_t*/) {
Operation.equals = function (opA /*:Operation_t*/, opB /*:Operation_t*/) {
return (opA.toRemove === opB.toRemove

@@ -135,3 +164,3 @@ && opA.toInsert === opB.toInsert

var lengthChange = Operation.lengthChange = function (op /*:Operation_t*/)
Operation.lengthChange = function (op /*:Operation_t*/)
{

@@ -145,3 +174,3 @@ if (Common.PARANOIA) { check(op); }

*/
var merge = Operation.merge = function (oldOpOrig /*:Operation_t*/, newOpOrig /*:Operation_t*/) {
Operation.merge = function (oldOpOrig /*:Operation_t*/, newOpOrig /*:Operation_t*/) {
if (Common.PARANOIA) {

@@ -202,3 +231,3 @@ check(newOpOrig);

*/
var shouldMerge = Operation.shouldMerge = function (oldOp /*:Operation_t*/, newOp /*:Operation_t*/)
Operation.shouldMerge = function (oldOp /*:Operation_t*/, newOp /*:Operation_t*/)
{

@@ -225,3 +254,3 @@ if (Common.PARANOIA) {

*/
var rebase = Operation.rebase = function (oldOp /*:Operation_t*/, newOp /*:Operation_t*/) {
Operation.rebase = function (oldOp /*:Operation_t*/, newOp /*:Operation_t*/) {
if (Common.PARANOIA) {

@@ -239,65 +268,4 @@ check(oldOp);

/**
* this is a lossy and dirty algorithm, everything else is nice but transformation
* has to be lossy because both operations have the same base and they diverge.
* This could be made nicer and/or tailored to a specific data type.
*
* @param toTransform the operation which is converted
* @param transformBy an existing operation which also has the same base.
* @return toTransform *or* null if the result is a no-op.
*/
var transform0 = Operation.transform0 = function (
text /*:string*/,
toTransform /*:Operation_t*/,
transformBy /*:Operation_t*/)
{
if (toTransform.offset > transformBy.offset) {
if (toTransform.offset > transformBy.offset + transformBy.toRemove) {
// simple rebase
return create(
toTransform.offset - transformBy.toRemove + transformBy.toInsert.length,
toTransform.toRemove,
toTransform.toInsert
);
}
var newToRemove =
toTransform.toRemove - (transformBy.offset + transformBy.toRemove - toTransform.offset);
if (newToRemove < 0) { newToRemove = 0; }
if (newToRemove === 0 && toTransform.toInsert.length === 0) { return null; }
return create(
transformBy.offset + transformBy.toInsert.length,
newToRemove,
toTransform.toInsert
);
}
// they don't touch, yay
if (toTransform.offset + toTransform.toRemove < transformBy.offset) { return toTransform; }
// Truncate what will be deleted...
var _newToRemove = transformBy.offset - toTransform.offset;
if (_newToRemove === 0 && toTransform.toInsert.length === 0) { return null; }
return create(toTransform.offset, _newToRemove, toTransform.toInsert);
};
/**
* @param toTransform the operation which is converted
* @param transformBy an existing operation which also has the same base.
* @return a modified clone of toTransform *or* toTransform itself if no change was made.
*/
var transform = Operation.transform = function (
text /*:string*/,
toTransform /*:Operation_t*/,
transformBy /*:Operation_t*/,
transformFunction /*:Operation_Transform_t*/)
{
if (Common.PARANOIA) {
check(toTransform);
check(transformBy);
}
var result = transformFunction(text, toTransform, transformBy);
if (Common.PARANOIA && result) { check(result); }
return result;
};
/** Used for testing. */
var random = Operation.random = function (docLength /*:number*/) {
Operation.random = function (docLength /*:number*/) {
Common.assert(Common.isUint(docLength));

@@ -304,0 +272,0 @@ var offset = Math.floor(Math.random() * 100000000 % docLength) || 0;

@@ -24,2 +24,4 @@ /*@flow*/

var nThen = require('nthen');
//var ChainPad = require('./ChainPad');
var TextTransformer = require('./transform/TextTransformer');

@@ -30,7 +32,7 @@ // These are fuzz tests so increasing this number might catch more errors.

var addOperationConst = function (origDoc, expectedDoc, operations) {
var docx = origDoc;
//var docx = origDoc;
var doc = origDoc;
var patch = Patch.create(Sha.hex_sha256(origDoc));
var rebasedOps = [];
//var rebasedOps = [];
for (var i = 0; i < operations.length; i++) {

@@ -117,2 +119,8 @@ Patch.addOperation(patch, operations[i]);

var convert = function (p) {
var out = Patch.create(p[0]);
p[1].forEach(function (o) { out.operations.push(Operation.create.apply(null, o)); });
return out;
};
var transformStatic = function () {

@@ -127,7 +135,2 @@ var p0 = [

];
var convert = function (p) {
var out = Patch.create(p[0]);
p[1].forEach(function (o) { out.operations.push(Operation.create.apply(null, o)); });
return out;
};

@@ -139,3 +142,3 @@ Patch.transform(

"D[`nosFrM`EpY\\Ib",
Operation.transform0
TextTransformer
);

@@ -148,3 +151,3 @@

Patch.transform(convert(p2), convert(p2), "SofyheYQWsva[NLAGkB", Operation.transform0);
Patch.transform(convert(p2), convert(p2), "SofyheYQWsva[NLAGkB", TextTransformer);
};

@@ -158,3 +161,3 @@

var patchAC = Patch.random(docA);
var patchBC = Patch.transform(patchAC, patchAB, docA, Operation.transform0);
var patchBC = Patch.transform(patchAC, patchAB, docA, TextTransformer);
var docB = Patch.apply(patchAB, docA);

@@ -179,3 +182,3 @@ Patch.apply(patchBC, docB);

var main = module.exports.main = function (cycles /*:number*/, callback /*:()=>void*/) {
module.exports.main = function (cycles /*:number*/, callback /*:()=>void*/) {
nThen(function (waitFor) {

@@ -182,0 +185,0 @@ simplify(cycles, waitFor());

@@ -43,2 +43,7 @@ /*@flow*/

export type Patch_Packed_t = Array<Operation_Packed_t|Sha256_t>;
export type Patch_Transform_t = (
toTransform:Array<Operation_t>,
transformBy:Array<Operation_t>,
state0:string
) => Array<Operation_t>;
*/

@@ -85,3 +90,3 @@

var toObj = Patch.toObj = function (patch /*:Patch_t*/) {
Patch.toObj = function (patch /*:Patch_t*/) {
if (Common.PARANOIA) { check(patch); }

@@ -97,3 +102,3 @@ var out /*:Array<Operation_Packed_t|Sha256_t>*/ = new Array(patch.operations.length+1);

var fromObj = Patch.fromObj = function (obj /*:Patch_Packed_t*/, isCheckpoint /*:?boolean*/) {
Patch.fromObj = function (obj /*:Patch_Packed_t*/, isCheckpoint /*:?boolean*/) {
Common.assert(Array.isArray(obj) && obj.length > 0);

@@ -141,3 +146,3 @@ var patch = create(Sha.check(obj[obj.length-1]), isCheckpoint);

var createCheckpoint = Patch.createCheckpoint = function (
Patch.createCheckpoint = function (
parentContent /*:string*/,

@@ -166,3 +171,3 @@ checkpointContent /*:string*/,

var merge = Patch.merge = function (oldPatch /*:Patch_t*/, newPatch /*:Patch_t*/) {
Patch.merge = function (oldPatch /*:Patch_t*/, newPatch /*:Patch_t*/) {
if (Common.PARANOIA) {

@@ -188,3 +193,3 @@ check(oldPatch);

var apply = Patch.apply = function (patch /*:Patch_t*/, doc /*:string*/)
Patch.apply = function (patch /*:Patch_t*/, doc /*:string*/)
{

@@ -203,3 +208,3 @@ if (Common.PARANOIA) {

var lengthChange = Patch.lengthChange = function (patch /*:Patch_t*/)
Patch.lengthChange = function (patch /*:Patch_t*/)
{

@@ -214,3 +219,3 @@ if (Common.PARANOIA) { check(patch); }

var invert = Patch.invert = function (patch /*:Patch_t*/, doc /*:string*/)
Patch.invert = function (patch /*:Patch_t*/, doc /*:string*/)
{

@@ -247,3 +252,3 @@ if (Common.PARANOIA) {

var simplify = Patch.simplify = function (
Patch.simplify = function (
patch /*:Patch_t*/,

@@ -279,3 +284,3 @@ doc /*:string*/,

var equals = Patch.equals = function (patchA /*:Patch_t*/, patchB /*:Patch_t*/) {
Patch.equals = function (patchA /*:Patch_t*/, patchB /*:Patch_t*/) {
if (patchA.operations.length !== patchB.operations.length) { return false; }

@@ -292,56 +297,52 @@ for (var i = 0; i < patchA.operations.length; i++) {

var transform = Patch.transform = function (
origToTransform /*:Patch_t*/,
Patch.transform = function (
toTransform /*:Patch_t*/,
transformBy /*:Patch_t*/,
doc /*:string*/,
transformFunction /*:Operation_Transform_t*/ )
patchTransformer /*:Patch_Transform_t*/ )
{
if (Common.PARANOIA) {
check(origToTransform, doc.length);
check(toTransform, doc.length);
check(transformBy, doc.length);
if (Sha.hex_sha256(doc) !== origToTransform.parentHash) { throw new Error("wrong hash"); }
if (Sha.hex_sha256(doc) !== toTransform.parentHash) { throw new Error("wrong hash"); }
}
if (origToTransform.parentHash !== transformBy.parentHash) { throw new Error(); }
var resultOfTransformBy = apply(transformBy, doc);
if (toTransform.parentHash !== transformBy.parentHash) { throw new Error(); }
var toTransform = clone(origToTransform);
var text = doc;
var afterTransformBy = Patch.apply(transformBy, doc);
var out = create(transformBy.mut.inverseOf
? transformBy.mut.inverseOf.parentHash
: Sha.hex_sha256(resultOfTransformBy));
for (var i = toTransform.operations.length-1; i >= 0; i--) {
var tti = toTransform.operations[i];
if (isCheckpointOp(tti, text)) { continue; }
for (var j = transformBy.operations.length-1; j >= 0; j--) {
if (isCheckpointOp(transformBy.operations[j], text)) { console.log('cpo'); continue; }
if (Common.DEBUG) {
console.log(
['TRANSFORM', text, tti, transformBy.operations[j]]
);
}
try {
tti = Operation.transform(text, tti, transformBy.operations[j], transformFunction);
} catch (e) {
console.error("The pluggable transform function threw an error, " +
"failing operational transformation");
console.error(e.stack);
return create(Sha.hex_sha256(resultOfTransformBy));
}
if (!tti) {
break;
}
}
if (tti) {
if (Common.PARANOIA) { Operation.check(tti, resultOfTransformBy.length); }
addOperation(out, tti);
}
: Sha.hex_sha256(afterTransformBy),
toTransform.isCheckpoint
);
if (transformBy.operations.length === 0) { return clone(toTransform); }
if (toTransform.operations.length === 0) {
if (toTransform.isCheckpoint) { throw new Error(); }
return out;
}
if (toTransform.isCheckpoint ||
(toTransform.operations.length === 1 && isCheckpointOp(toTransform.operations[0], doc)))
{
throw new Error("Attempting to transform a checkpoint, this should not happen");
}
if (transformBy.operations.length === 1 && isCheckpointOp(transformBy.operations[0], doc)) {
if (!transformBy.isCheckpoint) { throw new Error(); }
return toTransform;
}
if (transformBy.isCheckpoint) { throw new Error(); }
var ops = patchTransformer(toTransform.operations, transformBy.operations, doc);
Array.prototype.push.apply(out.operations, ops);
if (Common.PARANOIA) {
check(out, resultOfTransformBy.length);
check(out, afterTransformBy.length);
}
return out;
};
var random = Patch.random = function (doc /*:string*/, opCount /*:?number*/) {
Patch.random = function (doc /*:string*/, opCount /*:?number*/) {
Common.assert(typeof(doc) === 'string');

@@ -348,0 +349,0 @@ opCount = opCount || (Math.floor(Math.random() * 30) + 1);

@@ -20,5 +20,9 @@ /*

var nThen = require('nthen');
var Os = require('os');
//var Os = require('os');
(function buildChainpad() {
var cycles = 1;
var tests = [];
var timeOne;
nThen(function (waitFor) {
var g = new Glue();

@@ -33,2 +37,6 @@ g.basepath('./client');

g.include('./Operation.js');
g.include('./transform/TextTransformer.js');
g.include('./transform/NaiveJSONTransformer.js');
g.include('./transform/SmartJSONTransformer.js');
g.include('./Diff.js');
g.include('./sha256.js');

@@ -40,37 +48,48 @@ g.include('./sha256/exports.js');

g.include('./sha256/utils.js');
g.include('../node_modules/json.sortify/dist/JSON.sortify.js');
g.include('../node_modules/fast-diff/diff.js');
g.set('reset-exclude', true);
g.set('verbose', true);
g.set('umd', true);
g.export('ChainPad');
//g.set('command', 'uglifyjs --no-copyright --m "toplevel"');
g.render(Fs.createWriteStream('./chainpad.js'));
})();
var cycles = 1;
if (process.argv.indexOf('--cycles') !== -1) {
cycles = process.argv[process.argv.indexOf('--cycles')+1];
console.log("Running [" + cycles + "] test cycles");
}
var tests = [];
var timeOne = new Date().getTime();
nThen(function (waitFor) {
var nt = nThen;
Fs.readdir('./client/', waitFor(function (err, ret) {
g.render(waitFor(function (err, txt) {
if (err) { throw err; }
ret.forEach(function (file) {
if (/_test\.js$/.test(file)) {
nt = nt(function (waitFor) {
tests.push(file);
var test = require('./client/' + file);
console.log("Running Test " + file);
test.main(cycles, waitFor());
}).nThen;
}
});
nt(waitFor());
// make an anonymous define, don't insist on your name!
txt = txt.replace(
'"function"==typeof define&&define.amd&&define(f,function',
'"function"==typeof define&&define.amd&&define(function'
);
Fs.writeFile('./chainpad.js', txt, waitFor());
}));
}).nThen(function (waitFor) {
timeOne = new Date().getTime();
if (process.argv.indexOf('--cycles') !== -1) {
cycles = process.argv[process.argv.indexOf('--cycles')+1];
console.log("Running [" + cycles + "] test cycles");
}
var nt = nThen;
['./client/', './client/transform/'].forEach(function (path) {
Fs.readdir(path, waitFor(function (err, ret) {
if (err) { throw err; }
ret.forEach(function (file) {
if (/_test\.js$/.test(file)) {
nt = nt(function (waitFor) {
tests.push(file);
var test = require(path + file);
console.log("Running Test " + file);
test.main(cycles, waitFor());
}).nThen;
}
});
nt(waitFor());
}));
});
}).nThen(function () {
console.log("Tests passed.");
console.log('in ' + (new Date().getTime() - timeOne));
}).nThen(function (waitFor) {
}).nThen(function () {

@@ -81,3 +100,4 @@ var g = new Glue();

g.include('./');
g.include('../node_modules/nthen/lib/nthen.js');
g.include('../node_modules/nthen/index.js');
g.include('../node_modules/fast-diff/diff.js');
g.remap('testNames', JSON.stringify(tests));

@@ -84,0 +104,0 @@ g.export('AllTests');

{
"name": "chainpad",
"description": "Realtime Collaborative Editor Algorithm based on the Nakamoto BlockChain",
"version": "0.4.2",
"version": "5.2.0",
"dependencies": {
"fast-diff": "^1.1.2",
"gluejs": "~2.4.0",
"xmhell": "~0.1.2",
"nthen": "~0.1.1"
"json.sortify": "^2.2.2",
"nthen": "^0.1.5",
"xmhell": "~0.1.2"
},

@@ -10,0 +12,0 @@ "devDependencies": {

@@ -25,3 +25,3 @@ # ChainPad

This will run the tests and concatinate the js files into the resulting `chainpad.js` output file.
This will run the tests and concatenate the js files into the resulting `chainpad.js` output file.

@@ -54,3 +54,3 @@ ## The API

(however the number will be limited by the RTT to the server because ChainPad will only keep one
unacknoledged message on the wire at a time).
unacknowledged message on the wire at a time).
* **validateContent** (function) if specified, this function will be called during each patch and

@@ -61,6 +61,32 @@ receive the content of the document after the patch, if the document has semantic requirements

at an interval which is not in agreement with **checkpointInterval**. Default: *false*.
* **transformFunction** (function) if specified, this function will be substituted for the default
operational transformation function whenever two operations are applied simultaneously. Returning
`null` from the function will reject the resulting patch. For an example function, see
[chainpad-json-validator](https://github.com/xwiki-labs/chainpad-json-validator)
* **patchTransformer** (function) if specified, this function will be used for Operational
Transformation. You have 3 options which are packaged with ChainPad or you can implement your own.
* `ChainPad.TextTransformer` (this is default so you need not pass anything) if you're using
ChainPad on plain text, you probably want to use this.
* `ChainPad.SmartJSONTransformer` if you are using ChainPad to patch JSON data, you probably
want this.
* `ChainPad.NaiveJSONTransformer` this is effectively just TextTransformer with a
validation step to make sure the result is JSON, using this is not recommended.
* **operationSimplify** (function) This is an optional function which will override the function
`ChainPad.Operation.simplify` in case you want to use a different one. Simplify is used for "fixing"
operations which remove content and then put back the same content. The default simplify will not
create patches containing strings with single characters from
[surrogate pairs](https://en.wikipedia.org/wiki/UTF-16#U.2B0000_to_U.2BD7FF_and_U.2BE000_to_U.2BFFFF).
* **logLevel** (number) If this is zero, none of the normal logs will be printed.
* **userName** (string) This is a string which will appear at the beginning of all logs in the
console, if multiple ChainPad instances are running at the same time, this will help differentiate
them.
* **noPrune** (boolean) If this is true, history will not be pruned when a checkpoint is encountered.
Caution: this can end up occupying a lot of memory!
* **diffFunction** (function) This is a function which takes 2 strings and outputs and array of
Operations. If unspecified, ChainPad will use the `ChainPad.Diff` which is a smart diff algorithm
based on the one used by Fossel. The default diff function will not create patches containing strings
with single characters from
[surrogate pairs](https://en.wikipedia.org/wiki/UTF-16#U.2B0000_to_U.2BD7FF_and_U.2BE000_to_U.2BFFFF).
* **diffBlockSize** (number) This is an optional number which will inform the default diff function
`ChainPad.Diff` how big the rolling window should be. Smaller numbers imply more resource usage but
common areas within a pair of documents which are smaller than this number will not be seen.
The default is 8.
* **transformFunction** (function) This parameter has been removed, if you attempt to pass this
argument ChainPad will fail to start and throw an error.

@@ -225,2 +251,10 @@

### chainpad.getLag()
Tells the amount of lag between the last onMessage events being fired by chainpad and the callback.
Specifically this returns an object with lag and pending properties. Pending is true if a message
has been sent which has not yet been acknowledged. Lag is the amount of time between the previous
sent message and it's response or if the previously send message has not yet been acknowledged, it
is the amount of time since it was sent.
# Internals

@@ -268,3 +302,3 @@

can only be changed as a result of an incoming **Patch** from the server. The difference between
what the user sees in their screen and the *Authoriative Document* is represented by a **Patch**
what the user sees in their screen and the *Authoritative Document* is represented by a **Patch**
known as the *Uncommitted Work*.

@@ -310,3 +344,3 @@

Those with knowlege of Bitcoin will recognize this consensus protocol as inherently a
Those with knowledge of Bitcoin will recognize this consensus protocol as inherently a
Nakamoto Chain. Whereas Bitcoin uses blocks, each of which point to the previous block, ChainPad

@@ -313,0 +347,0 @@ uses **Patches** each of which point to the previous state of the document. In the case of ChainPad

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc