move to chainpad version 2
This commit is contained in:
parent
297d8c2d44
commit
976a08cc7a
@ -435,38 +435,20 @@ var REGISTER = Message.REGISTER = 0;
|
|||||||
var REGISTER_ACK = Message.REGISTER_ACK = 1;
|
var REGISTER_ACK = Message.REGISTER_ACK = 1;
|
||||||
var PATCH = Message.PATCH = 2;
|
var PATCH = Message.PATCH = 2;
|
||||||
var DISCONNECT = Message.DISCONNECT = 3;
|
var DISCONNECT = Message.DISCONNECT = 3;
|
||||||
var PING = Message.PING = 4;
|
|
||||||
var PONG = Message.PONG = 5;
|
|
||||||
|
|
||||||
var check = Message.check = function(msg) {
|
var check = Message.check = function(msg) {
|
||||||
Common.assert(msg.type === 'Message');
|
Common.assert(msg.type === 'Message');
|
||||||
Common.assert(typeof(msg.userName) === 'string');
|
|
||||||
Common.assert(typeof(msg.authToken) === 'string');
|
|
||||||
Common.assert(typeof(msg.channelId) === 'string');
|
|
||||||
|
|
||||||
if (msg.messageType === PATCH) {
|
if (msg.messageType === PATCH) {
|
||||||
Patch.check(msg.content);
|
Patch.check(msg.content);
|
||||||
Common.assert(typeof(msg.lastMsgHash) === 'string');
|
Common.assert(typeof(msg.lastMsgHash) === 'string');
|
||||||
} else if (msg.messageType === PING || msg.messageType === PONG) {
|
|
||||||
Common.assert(typeof(msg.lastMsgHash) === 'undefined');
|
|
||||||
Common.assert(typeof(msg.content) === 'number');
|
|
||||||
} else if (msg.messageType === REGISTER
|
|
||||||
|| msg.messageType === REGISTER_ACK
|
|
||||||
|| msg.messageType === DISCONNECT)
|
|
||||||
{
|
|
||||||
Common.assert(typeof(msg.lastMsgHash) === 'undefined');
|
|
||||||
Common.assert(typeof(msg.content) === 'undefined');
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error("invalid message type [" + msg.messageType + "]");
|
throw new Error("invalid message type [" + msg.messageType + "]");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var create = Message.create = function (userName, authToken, channelId, type, content, lastMsgHash) {
|
var create = Message.create = function (type, content, lastMsgHash) {
|
||||||
var msg = {
|
var msg = {
|
||||||
type: 'Message',
|
type: 'Message',
|
||||||
userName: userName,
|
|
||||||
authToken: authToken,
|
|
||||||
channelId: channelId,
|
|
||||||
messageType: type,
|
messageType: type,
|
||||||
content: content,
|
content: content,
|
||||||
lastMsgHash: lastMsgHash
|
lastMsgHash: lastMsgHash
|
||||||
@ -477,62 +459,67 @@ var create = Message.create = function (userName, authToken, channelId, type, co
|
|||||||
|
|
||||||
var toString = Message.toString = function (msg) {
|
var toString = Message.toString = function (msg) {
|
||||||
if (Common.PARANOIA) { check(msg); }
|
if (Common.PARANOIA) { check(msg); }
|
||||||
var prefix = msg.messageType + ':';
|
|
||||||
var content = '';
|
if (msg.messageType === PATCH) {
|
||||||
if (msg.messageType === REGISTER) {
|
return JSON.stringify([PATCH, Patch.toObj(msg.content), msg.lastMsgHash]);
|
||||||
content = JSON.stringify([REGISTER]);
|
} else {
|
||||||
} else if (msg.messageType === PING || msg.messageType === PONG) {
|
throw new Error();
|
||||||
content = JSON.stringify([msg.messageType, msg.content]);
|
|
||||||
} else if (msg.messageType === PATCH) {
|
|
||||||
content = JSON.stringify([PATCH, Patch.toObj(msg.content), msg.lastMsgHash]);
|
|
||||||
}
|
}
|
||||||
return msg.authToken.length + ":" + msg.authToken +
|
};
|
||||||
msg.userName.length + ":" + msg.userName +
|
|
||||||
msg.channelId.length + ":" + msg.channelId +
|
var discardBencode = function (msg, arr) {
|
||||||
content.length + ':' + content;
|
var len = msg.substring(0,msg.indexOf(':'));
|
||||||
|
msg = msg.substring(len.length+1);
|
||||||
|
var value = msg.substring(0,Number(len));
|
||||||
|
msg = msg.substring(value.length);
|
||||||
|
|
||||||
|
if (arr) { arr.push(value); }
|
||||||
|
return msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
var fromString = Message.fromString = function (str) {
|
var fromString = Message.fromString = function (str) {
|
||||||
var msg = str;
|
var msg = str;
|
||||||
|
|
||||||
var unameLen = msg.substring(0,msg.indexOf(':'));
|
if (str.charAt(0) === '[') {
|
||||||
msg = msg.substring(unameLen.length+1);
|
var m = JSON.parse(str);
|
||||||
var userName = msg.substring(0,Number(unameLen));
|
return create(m[0], Patch.fromObj(m[1]), m[2]);
|
||||||
msg = msg.substring(userName.length);
|
|
||||||
|
|
||||||
var channelIdLen = msg.substring(0,msg.indexOf(':'));
|
|
||||||
msg = msg.substring(channelIdLen.length+1);
|
|
||||||
var channelId = msg.substring(0,Number(channelIdLen));
|
|
||||||
msg = msg.substring(channelId.length);
|
|
||||||
|
|
||||||
var contentStrLen = msg.substring(0,msg.indexOf(':'));
|
|
||||||
msg = msg.substring(contentStrLen.length+1);
|
|
||||||
var contentStr = msg.substring(0,Number(contentStrLen));
|
|
||||||
|
|
||||||
Common.assert(contentStr.length === Number(contentStrLen));
|
|
||||||
|
|
||||||
var content = JSON.parse(contentStr);
|
|
||||||
var message;
|
|
||||||
if (content[0] === PATCH) {
|
|
||||||
message = create(userName, '', channelId, PATCH, Patch.fromObj(content[1]), content[2]);
|
|
||||||
} else if (content[0] === PING || content[0] === PONG) {
|
|
||||||
message = create(userName, '', channelId, content[0], content[1]);
|
|
||||||
} else {
|
} else {
|
||||||
message = create(userName, '', channelId, content[0]);
|
/* Just in case we receive messages in the old format,
|
||||||
|
we should try to parse them. We only need the content, though,
|
||||||
|
so just extract that and throw the rest away */
|
||||||
|
var last;
|
||||||
|
var parts = [];
|
||||||
|
|
||||||
|
// chop off all the bencoded components
|
||||||
|
while (msg) {
|
||||||
|
msg = discardBencode(msg, parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab the last component from the parts
|
||||||
|
// we don't need anything else
|
||||||
|
var contentStr = parts.slice(-1)[0];
|
||||||
|
|
||||||
|
var content = JSON.parse(contentStr);
|
||||||
|
var message;
|
||||||
|
if (content[0] === PATCH) {
|
||||||
|
message = create(userName, PATCH, Patch.fromObj(content[1]), content[2]);
|
||||||
|
} else if ([4,5].indexOf(content[0]) !== -1 /* === PING || content[0] === PONG*/) {
|
||||||
|
// it's a ping or pong, which we don't want to support anymore
|
||||||
|
message = create(userName, content[0], content[1]);
|
||||||
|
} else {
|
||||||
|
message = create(userName, content[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This check validates every operation in the patch.
|
||||||
|
check(message);
|
||||||
|
|
||||||
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
// This check validates every operation in the patch.
|
|
||||||
check(message);
|
|
||||||
|
|
||||||
return message
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var hashOf = Message.hashOf = function (msg) {
|
var hashOf = Message.hashOf = function (msg) {
|
||||||
if (Common.PARANOIA) { check(msg); }
|
if (Common.PARANOIA) { check(msg); }
|
||||||
var authToken = msg.authToken;
|
|
||||||
msg.authToken = '';
|
|
||||||
var hash = Sha.hex_sha256(toString(msg));
|
var hash = Sha.hex_sha256(toString(msg));
|
||||||
msg.authToken = authToken;
|
|
||||||
return hash;
|
return hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -554,10 +541,10 @@ var hashOf = Message.hashOf = function (msg) {
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
var Common = require('./Common');
|
var Common = module.exports.Common = require('./Common');
|
||||||
var Operation = module.exports.Operation = require('./Operation');
|
var Operation = module.exports.Operation = require('./Operation');
|
||||||
var Patch = require('./Patch');
|
var Patch = module.exports.Patch = require('./Patch');
|
||||||
var Message = require('./Message');
|
var Message = module.exports.Message = require('./Message');
|
||||||
var Sha = module.exports.Sha = require('./SHA256');
|
var Sha = module.exports.Sha = require('./SHA256');
|
||||||
|
|
||||||
var ChainPad = {};
|
var ChainPad = {};
|
||||||
@ -634,12 +621,7 @@ var sync = function (realtime) {
|
|||||||
if (realtime.best === realtime.initialMessage) {
|
if (realtime.best === realtime.initialMessage) {
|
||||||
msg = realtime.initialMessage;
|
msg = realtime.initialMessage;
|
||||||
} else {
|
} else {
|
||||||
msg = Message.create(realtime.userName,
|
msg = Message.create(Message.PATCH, realtime.uncommitted, realtime.best.hashOf);
|
||||||
realtime.authToken,
|
|
||||||
realtime.channelId,
|
|
||||||
Message.PATCH,
|
|
||||||
realtime.uncommitted,
|
|
||||||
realtime.best.hashOf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var strMsg = Message.toString(msg);
|
var strMsg = Message.toString(msg);
|
||||||
@ -647,6 +629,8 @@ var sync = function (realtime) {
|
|||||||
onMessage(realtime, strMsg, function (err) {
|
onMessage(realtime, strMsg, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
debug(realtime, "Posting to server failed [" + err + "]");
|
debug(realtime, "Posting to server failed [" + err + "]");
|
||||||
|
} else {
|
||||||
|
handleMessage(realtime, strMsg, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -670,46 +654,9 @@ var sync = function (realtime) {
|
|||||||
if (Common.PARANOIA) { check(realtime); }
|
if (Common.PARANOIA) { check(realtime); }
|
||||||
};
|
};
|
||||||
|
|
||||||
var getMessages = function (realtime) {
|
var create = ChainPad.create = function (config) {
|
||||||
realtime.registered = true;
|
|
||||||
/*var to = schedule(realtime, function () {
|
|
||||||
throw new Error("failed to connect to the server");
|
|
||||||
}, 5000);*/
|
|
||||||
var msg = Message.create(realtime.userName,
|
|
||||||
realtime.authToken,
|
|
||||||
realtime.channelId,
|
|
||||||
Message.REGISTER);
|
|
||||||
onMessage(realtime, Message.toString(msg), function (err) {
|
|
||||||
if (err) { throw err; }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var sendPing = function (realtime) {
|
|
||||||
realtime.pingSchedule = undefined;
|
|
||||||
realtime.lastPingTime = (new Date()).getTime();
|
|
||||||
var msg = Message.create(realtime.userName,
|
|
||||||
realtime.authToken,
|
|
||||||
realtime.channelId,
|
|
||||||
Message.PING,
|
|
||||||
realtime.lastPingTime);
|
|
||||||
onMessage(realtime, Message.toString(msg), function (err) {
|
|
||||||
if (err) { throw err; }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var onPong = function (realtime, msg) {
|
|
||||||
if (Common.PARANOIA) {
|
|
||||||
Common.assert(realtime.lastPingTime === Number(msg.content));
|
|
||||||
}
|
|
||||||
realtime.lastPingLag = (new Date()).getTime() - Number(msg.content);
|
|
||||||
realtime.lastPingTime = 0;
|
|
||||||
realtime.pingSchedule =
|
|
||||||
schedule(realtime, function () { sendPing(realtime); }, realtime.pingCycle);
|
|
||||||
};
|
|
||||||
|
|
||||||
var create = ChainPad.create = function (userName, authToken, channelId, initialState, config) {
|
|
||||||
|
|
||||||
config = config || {};
|
config = config || {};
|
||||||
|
var initialState = config.initialState || '';
|
||||||
|
|
||||||
var realtime = {
|
var realtime = {
|
||||||
type: 'ChainPad',
|
type: 'ChainPad',
|
||||||
@ -720,10 +667,6 @@ var create = ChainPad.create = function (userName, authToken, channelId, initial
|
|||||||
|
|
||||||
logLevel: typeof(config.logLevel) !== 'undefined'? config.logLevel: 1,
|
logLevel: typeof(config.logLevel) !== 'undefined'? config.logLevel: 1,
|
||||||
|
|
||||||
userName: userName,
|
|
||||||
authToken: authToken,
|
|
||||||
channelId: channelId,
|
|
||||||
|
|
||||||
/** A patch representing all uncommitted work. */
|
/** A patch representing all uncommitted work. */
|
||||||
uncommitted: null,
|
uncommitted: null,
|
||||||
|
|
||||||
@ -755,23 +698,13 @@ var create = ChainPad.create = function (userName, authToken, channelId, initial
|
|||||||
|
|
||||||
rootMessage: null,
|
rootMessage: null,
|
||||||
|
|
||||||
|
userName: config.userName || 'anonymous',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to the message which sets the initialState if applicable.
|
* Set to the message which sets the initialState if applicable.
|
||||||
* Reset to null after the initial message has been successfully broadcasted.
|
* Reset to null after the initial message has been successfully broadcasted.
|
||||||
*/
|
*/
|
||||||
initialMessage: null,
|
initialMessage: null,
|
||||||
|
|
||||||
userListChangeHandlers: [],
|
|
||||||
userList: [],
|
|
||||||
|
|
||||||
/** The schedule() for sending pings. */
|
|
||||||
pingSchedule: undefined,
|
|
||||||
|
|
||||||
lastPingLag: 0,
|
|
||||||
lastPingTime: 0,
|
|
||||||
|
|
||||||
/** Average number of milliseconds between pings. */
|
|
||||||
pingCycle: 5000
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Common.PARANOIA) {
|
if (Common.PARANOIA) {
|
||||||
@ -781,7 +714,7 @@ var create = ChainPad.create = function (userName, authToken, channelId, initial
|
|||||||
var zeroPatch = Patch.create(EMPTY_STR_HASH);
|
var zeroPatch = Patch.create(EMPTY_STR_HASH);
|
||||||
zeroPatch.inverseOf = Patch.invert(zeroPatch, '');
|
zeroPatch.inverseOf = Patch.invert(zeroPatch, '');
|
||||||
zeroPatch.inverseOf.inverseOf = zeroPatch;
|
zeroPatch.inverseOf.inverseOf = zeroPatch;
|
||||||
var zeroMsg = Message.create('', '', channelId, Message.PATCH, zeroPatch, ZERO);
|
var zeroMsg = Message.create(Message.PATCH, zeroPatch, ZERO);
|
||||||
zeroMsg.hashOf = Message.hashOf(zeroMsg);
|
zeroMsg.hashOf = Message.hashOf(zeroMsg);
|
||||||
zeroMsg.parentCount = 0;
|
zeroMsg.parentCount = 0;
|
||||||
realtime.messages[zeroMsg.hashOf] = zeroMsg;
|
realtime.messages[zeroMsg.hashOf] = zeroMsg;
|
||||||
@ -810,14 +743,10 @@ var create = ChainPad.create = function (userName, authToken, channelId, initial
|
|||||||
if (Common.PARANOIA) {
|
if (Common.PARANOIA) {
|
||||||
realtime.userInterfaceContent = initialState;
|
realtime.userInterfaceContent = initialState;
|
||||||
}
|
}
|
||||||
initialMessage = Message.create(realtime.userName,
|
initialMessage = Message.create(Message.PATCH, initialStatePatch, zeroMsg.hashOf);
|
||||||
realtime.authToken,
|
|
||||||
realtime.channelId,
|
|
||||||
Message.PATCH,
|
|
||||||
initialStatePatch,
|
|
||||||
zeroMsg.hashOf);
|
|
||||||
initialMessage.hashOf = Message.hashOf(initialMessage);
|
initialMessage.hashOf = Message.hashOf(initialMessage);
|
||||||
initialMessage.parentCount = 1;
|
initialMessage.parentCount = 1;
|
||||||
|
initialMessage.isFromMe = true;
|
||||||
|
|
||||||
realtime.messages[initialMessage.hashOf] = initialMessage;
|
realtime.messages[initialMessage.hashOf] = initialMessage;
|
||||||
(realtime.messagesByParent[initialMessage.lastMessageHash] || []).push(initialMessage);
|
(realtime.messagesByParent[initialMessage.lastMessageHash] || []).push(initialMessage);
|
||||||
@ -887,8 +816,10 @@ var parentCount = function (realtime, message) {
|
|||||||
return message.parentCount;
|
return message.parentCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
var applyPatch = function (realtime, author, patch) {
|
var applyPatch = function (realtime, isFromMe, patch) {
|
||||||
if (author === realtime.userName && !patch.isInitialStatePatch) {
|
Common.assert(patch);
|
||||||
|
Common.assert(patch.inverseOf);
|
||||||
|
if (isFromMe && !patch.isInitialStatePatch) {
|
||||||
var inverseOldUncommitted = Patch.invert(realtime.uncommitted, realtime.authDoc);
|
var inverseOldUncommitted = Patch.invert(realtime.uncommitted, realtime.authDoc);
|
||||||
var userInterfaceContent = Patch.apply(realtime.uncommitted, realtime.authDoc);
|
var userInterfaceContent = Patch.apply(realtime.uncommitted, realtime.authDoc);
|
||||||
if (Common.PARANOIA) {
|
if (Common.PARANOIA) {
|
||||||
@ -907,12 +838,14 @@ var applyPatch = function (realtime, author, patch) {
|
|||||||
realtime.authDoc = Patch.apply(patch, realtime.authDoc);
|
realtime.authDoc = Patch.apply(patch, realtime.authDoc);
|
||||||
|
|
||||||
if (Common.PARANOIA) {
|
if (Common.PARANOIA) {
|
||||||
|
Common.assert(realtime.uncommitted.parentHash === patch.inverseOf.parentHash);
|
||||||
|
Common.assert(Sha.hex_sha256(realtime.authDoc) === realtime.uncommitted.parentHash);
|
||||||
realtime.userInterfaceContent = Patch.apply(realtime.uncommitted, realtime.authDoc);
|
realtime.userInterfaceContent = Patch.apply(realtime.uncommitted, realtime.authDoc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var revertPatch = function (realtime, author, patch) {
|
var revertPatch = function (realtime, isFromMe, patch) {
|
||||||
applyPatch(realtime, author, patch.inverseOf);
|
applyPatch(realtime, isFromMe, patch.inverseOf);
|
||||||
};
|
};
|
||||||
|
|
||||||
var getBestChild = function (realtime, msg) {
|
var getBestChild = function (realtime, msg) {
|
||||||
@ -925,55 +858,23 @@ var getBestChild = function (realtime, msg) {
|
|||||||
return best;
|
return best;
|
||||||
};
|
};
|
||||||
|
|
||||||
var userListChange = function (realtime) {
|
var handleMessage = ChainPad.handleMessage = function (realtime, msgStr, isFromMe) {
|
||||||
for (var i = 0; i < realtime.userListChangeHandlers.length; i++) {
|
|
||||||
var list = [];
|
|
||||||
list.push.apply(list, realtime.userList);
|
|
||||||
realtime.userListChangeHandlers[i](list);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var handleMessage = ChainPad.handleMessage = function (realtime, msgStr) {
|
|
||||||
|
|
||||||
if (Common.PARANOIA) { check(realtime); }
|
if (Common.PARANOIA) { check(realtime); }
|
||||||
var msg = Message.fromString(msgStr);
|
var msg = Message.fromString(msgStr);
|
||||||
Common.assert(msg.channelId === realtime.channelId);
|
|
||||||
|
|
||||||
if (msg.messageType === Message.REGISTER_ACK) {
|
// These are all deprecated message types
|
||||||
debug(realtime, "registered");
|
if (['REGISTER', 'PONG', 'DISCONNECT'].map(function (x) {
|
||||||
realtime.registered = true;
|
return Message[x];
|
||||||
sendPing(realtime);
|
}).indexOf(msg.messageType) !== -1) {
|
||||||
return;
|
console.log("Deprecated message type: [%s]", msg.messageType);
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.messageType === Message.REGISTER) {
|
|
||||||
realtime.userList.push(msg.userName);
|
|
||||||
userListChange(realtime);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.messageType === Message.PONG) {
|
|
||||||
onPong(realtime, msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.messageType === Message.DISCONNECT) {
|
|
||||||
if (msg.userName === '') {
|
|
||||||
realtime.userList = [];
|
|
||||||
userListChange(realtime);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var idx = realtime.userList.indexOf(msg.userName);
|
|
||||||
if (Common.PARANOIA) { Common.assert(idx > -1); }
|
|
||||||
if (idx > -1) {
|
|
||||||
realtime.userList.splice(idx, 1);
|
|
||||||
userListChange(realtime);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise it's a disconnect.
|
// otherwise it's a disconnect.
|
||||||
if (msg.messageType !== Message.PATCH) { return; }
|
if (msg.messageType !== Message.PATCH) {
|
||||||
|
console.error("disconnect");
|
||||||
|
return; }
|
||||||
|
|
||||||
msg.hashOf = Message.hashOf(msg);
|
msg.hashOf = Message.hashOf(msg);
|
||||||
|
|
||||||
@ -1002,6 +903,7 @@ var handleMessage = ChainPad.handleMessage = function (realtime, msgStr) {
|
|||||||
// of this message fills in a hole in the chain which makes another patch better, swap to the
|
// of this message fills in a hole in the chain which makes another patch better, swap to the
|
||||||
// best child of this patch since longest chain always wins.
|
// best child of this patch since longest chain always wins.
|
||||||
msg = getBestChild(realtime, msg);
|
msg = getBestChild(realtime, msg);
|
||||||
|
msg.isFromMe = isFromMe;
|
||||||
var patch = msg.content;
|
var patch = msg.content;
|
||||||
|
|
||||||
// Find the ancestor of this patch which is in the main chain, reverting as necessary
|
// Find the ancestor of this patch which is in the main chain, reverting as necessary
|
||||||
@ -1040,6 +942,7 @@ var handleMessage = ChainPad.handleMessage = function (realtime, msgStr) {
|
|||||||
var authDocAtTimeOfPatch = realtime.authDoc;
|
var authDocAtTimeOfPatch = realtime.authDoc;
|
||||||
|
|
||||||
for (var i = 0; i < toRevert.length; i++) {
|
for (var i = 0; i < toRevert.length; i++) {
|
||||||
|
Common.assert(typeof(toRevert[i].content.inverseOf) !== 'undefined');
|
||||||
authDocAtTimeOfPatch = Patch.apply(toRevert[i].content.inverseOf, authDocAtTimeOfPatch);
|
authDocAtTimeOfPatch = Patch.apply(toRevert[i].content.inverseOf, authDocAtTimeOfPatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1086,13 +989,13 @@ var handleMessage = ChainPad.handleMessage = function (realtime, msgStr) {
|
|||||||
for (var i = 0; i < toRevert.length; i++) {
|
for (var i = 0; i < toRevert.length; i++) {
|
||||||
debug(realtime, "reverting [" + toRevert[i].hashOf + "]");
|
debug(realtime, "reverting [" + toRevert[i].hashOf + "]");
|
||||||
uncommittedPatch = Patch.merge(uncommittedPatch, toRevert[i].content.inverseOf);
|
uncommittedPatch = Patch.merge(uncommittedPatch, toRevert[i].content.inverseOf);
|
||||||
revertPatch(realtime, toRevert[i].userName, toRevert[i].content);
|
revertPatch(realtime, toRevert[i].isFromMe, toRevert[i].content);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < toApply.length; i++) {
|
for (var i = 0; i < toApply.length; i++) {
|
||||||
debug(realtime, "applying [" + toApply[i].hashOf + "]");
|
debug(realtime, "applying [" + toApply[i].hashOf + "]");
|
||||||
uncommittedPatch = Patch.merge(uncommittedPatch, toApply[i].content);
|
uncommittedPatch = Patch.merge(uncommittedPatch, toApply[i].content);
|
||||||
applyPatch(realtime, toApply[i].userName, toApply[i].content);
|
applyPatch(realtime, toApply[i].isFromMe, toApply[i].content);
|
||||||
}
|
}
|
||||||
|
|
||||||
uncommittedPatch = Patch.merge(uncommittedPatch, realtime.uncommitted);
|
uncommittedPatch = Patch.merge(uncommittedPatch, realtime.uncommitted);
|
||||||
@ -1125,22 +1028,6 @@ var handleMessage = ChainPad.handleMessage = function (realtime, msgStr) {
|
|||||||
if (Common.PARANOIA) { check(realtime); }
|
if (Common.PARANOIA) { check(realtime); }
|
||||||
};
|
};
|
||||||
|
|
||||||
var wasEverState = function (content, realtime) {
|
|
||||||
Common.assert(typeof(content) === 'string');
|
|
||||||
// without this we would never get true on the ^HEAD
|
|
||||||
if (realtime.authDoc === content) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash = Sha.hex_sha256(content);
|
|
||||||
|
|
||||||
var patchMsg = realtime.best;
|
|
||||||
do {
|
|
||||||
if (patchMsg.content.parentHash === hash) { return true; }
|
|
||||||
} while ((patchMsg = getParent(realtime, patchMsg)));
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
var getDepthOfState = function (content, minDepth, realtime) {
|
var getDepthOfState = function (content, minDepth, realtime) {
|
||||||
Common.assert(typeof(content) === 'string');
|
Common.assert(typeof(content) === 'string');
|
||||||
|
|
||||||
@ -1169,47 +1056,29 @@ var getDepthOfState = function (content, minDepth, realtime) {
|
|||||||
}
|
}
|
||||||
depth++;
|
depth++;
|
||||||
} while ((patchMsg = getParent(realtime, patchMsg)));
|
} while ((patchMsg = getParent(realtime, patchMsg)));
|
||||||
return;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.create = function (userName, authToken, channelId, initialState, conf) {
|
module.exports.create = function (conf) {
|
||||||
Common.assert(typeof(userName) === 'string');
|
var realtime = ChainPad.create(conf);
|
||||||
Common.assert(typeof(authToken) === 'string');
|
|
||||||
Common.assert(typeof(channelId) === 'string');
|
|
||||||
Common.assert(typeof(initialState) === 'string');
|
|
||||||
var realtime = ChainPad.create(userName, authToken, channelId, initialState, conf);
|
|
||||||
return {
|
return {
|
||||||
onPatch: enterChainPad(realtime, function (handler) {
|
onPatch: enterChainPad(realtime, function (handler) {
|
||||||
Common.assert(typeof(handler) === 'function');
|
Common.assert(typeof(handler) === 'function');
|
||||||
realtime.patchHandlers.push(handler);
|
realtime.patchHandlers.push(handler);
|
||||||
}),
|
}),
|
||||||
onRemove: enterChainPad(realtime, function (handler) {
|
|
||||||
Common.assert(typeof(handler) === 'function');
|
patch: enterChainPad(realtime, function (offset, count, chars) {
|
||||||
realtime.opHandlers.unshift(function (op) {
|
doOperation(realtime, Operation.create(offset, count, chars));
|
||||||
if (op.toRemove > 0) { handler(op.offset, op.toRemove); }
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
onInsert: enterChainPad(realtime, function (handler) {
|
|
||||||
Common.assert(typeof(handler) === 'function');
|
|
||||||
realtime.opHandlers.push(function (op) {
|
|
||||||
if (op.toInsert.length > 0) { handler(op.offset, op.toInsert); }
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
remove: enterChainPad(realtime, function (offset, numChars) {
|
|
||||||
doOperation(realtime, Operation.create(offset, numChars, ''));
|
|
||||||
}),
|
|
||||||
insert: enterChainPad(realtime, function (offset, str) {
|
|
||||||
doOperation(realtime, Operation.create(offset, 0, str));
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
onMessage: enterChainPad(realtime, function (handler) {
|
onMessage: enterChainPad(realtime, function (handler) {
|
||||||
Common.assert(typeof(handler) === 'function');
|
Common.assert(typeof(handler) === 'function');
|
||||||
realtime.messageHandlers.push(handler);
|
realtime.messageHandlers.push(handler);
|
||||||
}),
|
}),
|
||||||
message: enterChainPad(realtime, function (message) {
|
message: enterChainPad(realtime, function (message) {
|
||||||
handleMessage(realtime, message);
|
handleMessage(realtime, message, false);
|
||||||
}),
|
}),
|
||||||
start: enterChainPad(realtime, function () {
|
start: enterChainPad(realtime, function () {
|
||||||
getMessages(realtime);
|
|
||||||
if (realtime.syncSchedule) { unschedule(realtime, realtime.syncSchedule); }
|
if (realtime.syncSchedule) { unschedule(realtime, realtime.syncSchedule); }
|
||||||
realtime.syncSchedule = schedule(realtime, function () { sync(realtime); });
|
realtime.syncSchedule = schedule(realtime, function () { sync(realtime); });
|
||||||
}),
|
}),
|
||||||
@ -1221,19 +1090,7 @@ module.exports.create = function (userName, authToken, channelId, initialState,
|
|||||||
}),
|
}),
|
||||||
getAuthDoc: function () { return realtime.authDoc; },
|
getAuthDoc: function () { return realtime.authDoc; },
|
||||||
getUserDoc: function () { return Patch.apply(realtime.uncommitted, realtime.authDoc); },
|
getUserDoc: function () { return Patch.apply(realtime.uncommitted, realtime.authDoc); },
|
||||||
onUserListChange: enterChainPad(realtime, function (handler) {
|
|
||||||
Common.assert(typeof(handler) === 'function');
|
|
||||||
realtime.userListChangeHandlers.push(handler);
|
|
||||||
}),
|
|
||||||
getLag: function () {
|
|
||||||
if (realtime.lastPingTime) {
|
|
||||||
return { waiting:1, lag: (new Date()).getTime() - realtime.lastPingTime };
|
|
||||||
}
|
|
||||||
return { waiting:0, lag: realtime.lastPingLag };
|
|
||||||
},
|
|
||||||
wasEverState: function (content) {
|
|
||||||
return wasEverState(content, realtime);
|
|
||||||
},
|
|
||||||
getDepthOfState: function (content, minDepth) {
|
getDepthOfState: function (content, minDepth) {
|
||||||
return getDepthOfState(content, minDepth, realtime);
|
return getDepthOfState(content, minDepth, realtime);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,36 +22,12 @@ define([
|
|||||||
return Nacl.util.encodeUTF8(unpacked);
|
return Nacl.util.encodeUTF8(unpacked);
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is crap because of bencoding messages... it should go away....
|
|
||||||
var splitMessage = function (msg, sending) {
|
|
||||||
var idx = 0;
|
|
||||||
var nl;
|
|
||||||
for (var i = ((sending) ? 0 : 1); i < 3; i++) {
|
|
||||||
nl = msg.indexOf(':',idx);
|
|
||||||
idx = nl + Number(msg.substring(idx,nl)) + 1;
|
|
||||||
}
|
|
||||||
return [ msg.substring(0,idx), msg.substring(msg.indexOf(':',idx) + 1) ];
|
|
||||||
};
|
|
||||||
|
|
||||||
var encrypt = module.exports.encrypt = function (msg, key) {
|
var encrypt = module.exports.encrypt = function (msg, key) {
|
||||||
var spl = splitMessage(msg, true);
|
return encryptStr(msg, key);
|
||||||
var json = JSON.parse(spl[1]);
|
|
||||||
// non-patches are not encrypted.
|
|
||||||
if (json[0] !== 2) { return msg; }
|
|
||||||
json[1] = encryptStr(JSON.stringify(json[1]), key);
|
|
||||||
var res = JSON.stringify(json);
|
|
||||||
return spl[0] + res.length + ':' + res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var decrypt = module.exports.decrypt = function (msg, key) {
|
var decrypt = module.exports.decrypt = function (msg, key) {
|
||||||
var spl = splitMessage(msg, false);
|
return decryptStr(msg, key);
|
||||||
var json = JSON.parse(spl[1]);
|
|
||||||
// non-patches are not encrypted.
|
|
||||||
if (json[0] !== 2) { return msg; }
|
|
||||||
if (typeof(json[1]) !== 'string') { throw new Error(); }
|
|
||||||
json[1] = JSON.parse(decryptStr(json[1], key));
|
|
||||||
var res = JSON.stringify(json);
|
|
||||||
return spl[0] + res.length + ':' + res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var parseKey = module.exports.parseKey = function (str) {
|
var parseKey = module.exports.parseKey = function (str) {
|
||||||
|
|||||||
@ -288,4 +288,4 @@ define(function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return { connect: connect };
|
return { connect: connect };
|
||||||
});
|
});
|
||||||
|
|||||||
@ -37,6 +37,8 @@ define([
|
|||||||
verbose = function (x) { console.log(x); };
|
verbose = function (x) { console.log(x); };
|
||||||
verbose = function () {}; // comment out to enable verbose logging
|
verbose = function () {}; // comment out to enable verbose logging
|
||||||
|
|
||||||
|
var unBencode = function (str) { return str.replace(/^\d+:/, ''); };
|
||||||
|
|
||||||
var start = module.exports.start =
|
var start = module.exports.start =
|
||||||
function (config)
|
function (config)
|
||||||
{
|
{
|
||||||
@ -59,27 +61,7 @@ define([
|
|||||||
var realtime;
|
var realtime;
|
||||||
|
|
||||||
var parseMessage = function (msg) {
|
var parseMessage = function (msg) {
|
||||||
var res ={};
|
return unBencode(msg);//.slice(msg.indexOf(':[') + 1);
|
||||||
// two or more? use a for
|
|
||||||
['pass','user','channelId','content'].forEach(function(attr){
|
|
||||||
var len=msg.slice(0,msg.indexOf(':')),
|
|
||||||
// taking an offset lets us slice out the prop
|
|
||||||
// and saves us one string copy
|
|
||||||
o=len.length+1,
|
|
||||||
prop=res[attr]=msg.slice(o,Number(len)+o);
|
|
||||||
// slice off the property and its descriptor
|
|
||||||
msg = msg.slice(prop.length+o);
|
|
||||||
});
|
|
||||||
// content is the only attribute that's not a string
|
|
||||||
res.content=JSON.parse(res.content);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
var mkMessage = function (user, chan, content) {
|
|
||||||
content = JSON.stringify(content);
|
|
||||||
return user.length + ':' + user +
|
|
||||||
chan.length + ':' + chan +
|
|
||||||
content.length + ':' + content;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var userList = {
|
var userList = {
|
||||||
@ -137,6 +119,12 @@ define([
|
|||||||
config.onLocal();
|
config.onLocal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slice off the bencoded header
|
||||||
|
// Why are we getting bencoded stuff to begin with?
|
||||||
|
// FIXME this shouldn't be necessary
|
||||||
|
message = unBencode(message);//.slice(message.indexOf(':[') + 1);
|
||||||
|
|
||||||
// pass the message into Chainpad
|
// pass the message into Chainpad
|
||||||
realtime.message(message);
|
realtime.message(message);
|
||||||
};
|
};
|
||||||
@ -154,10 +142,7 @@ define([
|
|||||||
// shim between chainpad and netflux
|
// shim between chainpad and netflux
|
||||||
chainpadAdapter = {
|
chainpadAdapter = {
|
||||||
msgIn : function(peerId, msg) {
|
msgIn : function(peerId, msg) {
|
||||||
var parsed = parseMessage(msg);
|
var message = parseMessage(msg);
|
||||||
// Remove the password from the message
|
|
||||||
var passLen = msg.substring(0,msg.indexOf(':'));
|
|
||||||
var message = msg.substring(passLen.length+1 + Number(passLen));
|
|
||||||
try {
|
try {
|
||||||
var decryptedMsg = Crypto.decrypt(message, cryptKey);
|
var decryptedMsg = Crypto.decrypt(message, cryptKey);
|
||||||
messagesHistory.push(decryptedMsg);
|
messagesHistory.push(decryptedMsg);
|
||||||
@ -166,33 +151,24 @@ define([
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
msgOut : function(msg, wc) {
|
msgOut : function(msg, wc) {
|
||||||
var parsed = parseMessage(msg);
|
try {
|
||||||
if(parsed.content[0] === 0) { // We're registering : send a REGISTER_ACK to Chainpad
|
return Crypto.encrypt(msg, cryptKey);
|
||||||
onMessage('', '1:y'+mkMessage('', channel, [1,0]));
|
} catch (err) {
|
||||||
return;
|
console.log(msg);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
if(parsed.content[0] === 4) { // PING message from Chainpad
|
|
||||||
parsed.content[0] = 5;
|
|
||||||
onMessage('', '1:y'+mkMessage(parsed.user, parsed.channelId, parsed.content));
|
|
||||||
// wc.sendPing();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return Crypto.encrypt(msg, cryptKey);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var createRealtime = function(chan) {
|
var createRealtime = function(chan) {
|
||||||
return ChainPad.create(userName,
|
return ChainPad.create({
|
||||||
passwd,
|
userName: userName,
|
||||||
channel,
|
initialState: config.initialState,
|
||||||
config.initialState || '',
|
transformFunction: config.transformFunction,
|
||||||
{
|
logLevel: typeof(config.logLevel) !== 'undefined'? config.logLevel : 1
|
||||||
transformFunction: config.transformFunction,
|
});
|
||||||
logLevel: typeof(config.logLevel) !== 'undefined'? config.logLevel : 1
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -225,13 +201,12 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sending a message...
|
// Sending a message...
|
||||||
realtime.onMessage(function(message) {
|
realtime.onMessage(function(message, cb) {
|
||||||
// Filter messages sent by Chainpad to make it compatible with Netflux
|
// Filter messages sent by Chainpad to make it compatible with Netflux
|
||||||
message = chainpadAdapter.msgOut(message, wc);
|
message = chainpadAdapter.msgOut(message, wc);
|
||||||
if(message) {
|
if(message) {
|
||||||
wc.bcast(message).then(function() {
|
wc.bcast(message).then(function() {
|
||||||
// Send the message back to Chainpad once it is sent to the recipients.
|
cb();
|
||||||
onMessage(wc.myID, message);
|
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
// The message has not been sent, display the error.
|
// The message has not been sent, display the error.
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -283,8 +258,6 @@ define([
|
|||||||
// pass messages that come out of netflux into our local handler
|
// pass messages that come out of netflux into our local handler
|
||||||
|
|
||||||
network.on('disconnect', function (evt) {
|
network.on('disconnect', function (evt) {
|
||||||
// TODO also abort if Netflux times out
|
|
||||||
// that will be managed in Netflux-client.js
|
|
||||||
if (config.onAbort) {
|
if (config.onAbort) {
|
||||||
config.onAbort({
|
config.onAbort({
|
||||||
reason: evt.reason
|
reason: evt.reason
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user