included connector type
This commit is contained in:
parent
f835a72151
commit
58a479be9b
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,7 +1,13 @@
|
|||||||
var adaptConnector;
|
var ConnectorClass, adaptConnector;
|
||||||
|
|
||||||
|
ConnectorClass = require("./ConnectorClass");
|
||||||
|
|
||||||
adaptConnector = function(connector, engine, HB, execution_listener) {
|
adaptConnector = function(connector, engine, HB, execution_listener) {
|
||||||
var applyHB, encode_state_vector, getHB, getStateVector, parse_state_vector, send_;
|
var applyHB, encode_state_vector, f, getHB, getStateVector, name, parse_state_vector, send_;
|
||||||
|
for (name in ConnectorClass) {
|
||||||
|
f = ConnectorClass[name];
|
||||||
|
connector[name] = f;
|
||||||
|
}
|
||||||
send_ = function(o) {
|
send_ = function(o) {
|
||||||
if (o.uid.creator === HB.getUserId() && (typeof o.uid.op_number !== "string")) {
|
if (o.uid.creator === HB.getUserId() && (typeof o.uid.op_number !== "string")) {
|
||||||
return connector.broadcast(o);
|
return connector.broadcast(o);
|
||||||
@ -12,7 +18,7 @@ adaptConnector = function(connector, engine, HB, execution_listener) {
|
|||||||
}
|
}
|
||||||
execution_listener.push(send_);
|
execution_listener.push(send_);
|
||||||
encode_state_vector = function(v) {
|
encode_state_vector = function(v) {
|
||||||
var name, value, _results;
|
var value, _results;
|
||||||
_results = [];
|
_results = [];
|
||||||
for (name in v) {
|
for (name in v) {
|
||||||
value = v[name];
|
value = v[name];
|
||||||
@ -55,6 +61,7 @@ adaptConnector = function(connector, engine, HB, execution_listener) {
|
|||||||
connector.getStateVector = getStateVector;
|
connector.getStateVector = getStateVector;
|
||||||
connector.getHB = getHB;
|
connector.getHB = getHB;
|
||||||
connector.applyHB = applyHB;
|
connector.applyHB = applyHB;
|
||||||
|
connector.receive_handlers = [];
|
||||||
connector.receive_handlers.push(function(sender, op) {
|
connector.receive_handlers.push(function(sender, op) {
|
||||||
if (op.uid.creator !== HB.getUserId()) {
|
if (op.uid.creator !== HB.getUserId()) {
|
||||||
return engine.applyOp(op);
|
return engine.applyOp(op);
|
||||||
|
304
build/node/ConnectorClass.js
Normal file
304
build/node/ConnectorClass.js
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
module.exports = {
|
||||||
|
init: function(options) {
|
||||||
|
var req;
|
||||||
|
req = (function(_this) {
|
||||||
|
return function(name, choices) {
|
||||||
|
if (options[name] != null) {
|
||||||
|
if ((choices == null) || choices.some(function(c) {
|
||||||
|
return c === options[name];
|
||||||
|
})) {
|
||||||
|
return _this[name] = options[name];
|
||||||
|
} else {
|
||||||
|
throw new Error("You can set the '" + name + "' option to one of the following choices: " + JSON.encode(choices));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error("You must specify " + name + ", when initializing the Connector!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(this);
|
||||||
|
req("syncMode", ["syncAll", "master-slave"]);
|
||||||
|
req("role", ["master", "slave"]);
|
||||||
|
req("user_id");
|
||||||
|
this.on_user_id_set(this.user_id);
|
||||||
|
if (this.role === "master") {
|
||||||
|
this.syncMode = "syncAll";
|
||||||
|
}
|
||||||
|
this.is_synced = false;
|
||||||
|
this.is_syncing = false;
|
||||||
|
this.connections = {};
|
||||||
|
this.is_bound_to_y = false;
|
||||||
|
this.connections = {};
|
||||||
|
return this.current_sync_target = null;
|
||||||
|
},
|
||||||
|
isRoleMaster: function() {
|
||||||
|
return this.role === "master";
|
||||||
|
},
|
||||||
|
isRoleSlave: function() {
|
||||||
|
return this.role === "slave";
|
||||||
|
},
|
||||||
|
findNewSyncTarget: function() {
|
||||||
|
var c, user, _ref;
|
||||||
|
this.current_sync_target = null;
|
||||||
|
if (this.syncMode === "syncAll") {
|
||||||
|
_ref = this.connections;
|
||||||
|
for (user in _ref) {
|
||||||
|
c = _ref[user];
|
||||||
|
if (!c.is_synced) {
|
||||||
|
this.performSync(user);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
userLeft: function(user) {
|
||||||
|
delete this.connections[user];
|
||||||
|
return this.findNewSyncTarget();
|
||||||
|
},
|
||||||
|
userJoined: function(user, role) {
|
||||||
|
if (role == null) {
|
||||||
|
throw new Error("Internal: You must specify the role of the joined user!");
|
||||||
|
}
|
||||||
|
this.connections[user] = {
|
||||||
|
is_synced: false
|
||||||
|
};
|
||||||
|
if ((!this.is_synced) || this.syncMode === "syncAll") {
|
||||||
|
if (this.syncMode === "syncAll") {
|
||||||
|
return this.performSync(user);
|
||||||
|
} else if (role === "master") {
|
||||||
|
return this.performSyncWithMaster(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
whenSynced: function(args) {
|
||||||
|
if (args.constructore === Function) {
|
||||||
|
args = [args];
|
||||||
|
}
|
||||||
|
if (this.is_synced) {
|
||||||
|
return args[0].apply(this, args.slice(1));
|
||||||
|
} else {
|
||||||
|
if (this.compute_when_synced == null) {
|
||||||
|
this.compute_when_synced = [];
|
||||||
|
}
|
||||||
|
return this.compute_when_synced.push(args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onReceive: function(f) {
|
||||||
|
return this.receive_handlers.push(f);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Broadcast a message to all connected peers.
|
||||||
|
* @param message {Object} The message to broadcast.
|
||||||
|
*
|
||||||
|
broadcast: (message)->
|
||||||
|
throw new Error "You must implement broadcast!"
|
||||||
|
|
||||||
|
*
|
||||||
|
* Send a message to a peer, or set of peers
|
||||||
|
*
|
||||||
|
send: (peer_s, message)->
|
||||||
|
throw new Error "You must implement send!"
|
||||||
|
*/
|
||||||
|
performSync: function(user) {
|
||||||
|
if (this.current_sync_target == null) {
|
||||||
|
this.current_sync_target = user;
|
||||||
|
return this.send(user, {
|
||||||
|
sync_step: "getHB",
|
||||||
|
data: this.getStateVector()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
performSyncWithMaster: function(user) {
|
||||||
|
var hb, o, _hb, _i, _len;
|
||||||
|
if (!this.is_syncing) {
|
||||||
|
this.current_sync_target = user;
|
||||||
|
this.is_syncing = true;
|
||||||
|
this.send(user, {
|
||||||
|
sync_step: "getHB",
|
||||||
|
send_again: "true",
|
||||||
|
data: []
|
||||||
|
});
|
||||||
|
hb = this.getHB([]).hb;
|
||||||
|
_hb = [];
|
||||||
|
for (_i = 0, _len = hb.length; _i < _len; _i++) {
|
||||||
|
o = hb[_i];
|
||||||
|
_hb.push(o);
|
||||||
|
if (_hb.length > 30) {
|
||||||
|
this.broadcast({
|
||||||
|
sync_step: "applyHB_",
|
||||||
|
data: _hb
|
||||||
|
});
|
||||||
|
_hb = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.broadcast({
|
||||||
|
sync_step: "applyHB",
|
||||||
|
data: _hb
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setStateSynced: function() {
|
||||||
|
var f, _i, _len, _ref;
|
||||||
|
if (!this.is_synced) {
|
||||||
|
this.is_synced = true;
|
||||||
|
_ref = this.compute_when_synced;
|
||||||
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
|
f = _ref[_i];
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
delete this.compute_when_synced;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
receiveMessage: function(sender, res) {
|
||||||
|
var data, f, hb, o, send_again, _hb, _i, _j, _len, _len1, _ref, _results;
|
||||||
|
if (res.sync_step == null) {
|
||||||
|
_ref = this.receive_handlers;
|
||||||
|
_results = [];
|
||||||
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
|
f = _ref[_i];
|
||||||
|
_results.push(f(sender, res));
|
||||||
|
}
|
||||||
|
return _results;
|
||||||
|
} else {
|
||||||
|
if (res.sync_step === "getHB") {
|
||||||
|
data = this.getHB(res.data);
|
||||||
|
hb = data.hb;
|
||||||
|
_hb = [];
|
||||||
|
for (_j = 0, _len1 = hb.length; _j < _len1; _j++) {
|
||||||
|
o = hb[_j];
|
||||||
|
_hb.push(o);
|
||||||
|
if (_hb.length > 30) {
|
||||||
|
this.send(sender, {
|
||||||
|
sync_step: "applyHB_",
|
||||||
|
data: _hb
|
||||||
|
});
|
||||||
|
_hb = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.send(sender, {
|
||||||
|
sync_step: "applyHB",
|
||||||
|
data: _hb
|
||||||
|
});
|
||||||
|
if (res.send_again != null) {
|
||||||
|
send_again = (function(_this) {
|
||||||
|
return function(sv) {
|
||||||
|
return function() {
|
||||||
|
hb = _this.getHB(sv).hb;
|
||||||
|
return _this.send(sender, {
|
||||||
|
sync_step: "applyHB",
|
||||||
|
data: hb,
|
||||||
|
sent_again: "true"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})(this)(data.state_vector);
|
||||||
|
return setTimeout(send_again, 3000);
|
||||||
|
}
|
||||||
|
} else if (res.sync_step === "applyHB") {
|
||||||
|
this.applyHB(res.data);
|
||||||
|
if ((this.syncMode === "syncAll" || (res.sent_again != null)) && !this.is_synced) {
|
||||||
|
this.setStateSynced();
|
||||||
|
this.connections[sender].is_synced = true;
|
||||||
|
return this.findNewSyncTarget();
|
||||||
|
}
|
||||||
|
} else if (res.sync_step === "applyHB_") {
|
||||||
|
return this.applyHB(res.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseMessageFromXml: function(m) {
|
||||||
|
var parse_array, parse_object;
|
||||||
|
parse_array = function(node) {
|
||||||
|
var n, _i, _len, _ref, _results;
|
||||||
|
_ref = node.children;
|
||||||
|
_results = [];
|
||||||
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||||
|
n = _ref[_i];
|
||||||
|
if (n.getAttribute("isArray") === "true") {
|
||||||
|
_results.push(parse_array(n));
|
||||||
|
} else {
|
||||||
|
_results.push(parse_object(n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _results;
|
||||||
|
};
|
||||||
|
parse_object = function(node) {
|
||||||
|
var int, json, n, name, value, _i, _len, _ref, _ref1;
|
||||||
|
json = {};
|
||||||
|
_ref = node.attrs;
|
||||||
|
for (name in _ref) {
|
||||||
|
value = _ref[name];
|
||||||
|
int = parseInt(value);
|
||||||
|
if (isNaN(int) || ("" + int) !== value) {
|
||||||
|
json[name] = value;
|
||||||
|
} else {
|
||||||
|
json[name] = int;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ref1 = node.children;
|
||||||
|
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
|
||||||
|
n = _ref1[_i];
|
||||||
|
name = n.name;
|
||||||
|
if (n.getAttribute("isArray") === "true") {
|
||||||
|
json[name] = parse_array(n);
|
||||||
|
} else {
|
||||||
|
json[name] = parse_object(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
return parse_object(m);
|
||||||
|
},
|
||||||
|
encodeMessageToXml: function(m, json) {
|
||||||
|
var encode_array, encode_object;
|
||||||
|
encode_object = function(m, json) {
|
||||||
|
var name, value;
|
||||||
|
for (name in json) {
|
||||||
|
value = json[name];
|
||||||
|
if (value == null) {
|
||||||
|
|
||||||
|
} else if (value.constructor === Object) {
|
||||||
|
encode_object(m.c(name), value);
|
||||||
|
} else if (value.constructor === Array) {
|
||||||
|
encode_array(m.c(name), value);
|
||||||
|
} else {
|
||||||
|
m.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
};
|
||||||
|
encode_array = function(m, array) {
|
||||||
|
var e, _i, _len;
|
||||||
|
m.setAttribute("isArray", "true");
|
||||||
|
for (_i = 0, _len = array.length; _i < _len; _i++) {
|
||||||
|
e = array[_i];
|
||||||
|
if (e.constructor === Object) {
|
||||||
|
encode_object(m.c("array-element"), e);
|
||||||
|
} else {
|
||||||
|
encode_array(m.c("array-element"), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
};
|
||||||
|
if (json.constructor === Object) {
|
||||||
|
return encode_object(m.c("y", {
|
||||||
|
xmlns: "http://y.ninja/connector-stanza"
|
||||||
|
}), json);
|
||||||
|
} else if (json.constructor === Array) {
|
||||||
|
return encode_array(m.c("y", {
|
||||||
|
xmlns: "http://y.ninja/connector-stanza"
|
||||||
|
}), json);
|
||||||
|
} else {
|
||||||
|
throw new Error("I can't encode this json!");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setIsBoundToY: function() {
|
||||||
|
if (typeof this.on_bound_to_y === "function") {
|
||||||
|
this.on_bound_to_y();
|
||||||
|
}
|
||||||
|
delete this.when_bound_to_y;
|
||||||
|
return this.is_bound_to_y = true;
|
||||||
|
}
|
||||||
|
};
|
@ -30,8 +30,10 @@ HistoryBuffer = (function() {
|
|||||||
this.buffer[id] = own;
|
this.buffer[id] = own;
|
||||||
delete this.buffer[this.user_id];
|
delete this.buffer[this.user_id];
|
||||||
}
|
}
|
||||||
this.operation_counter[id] = this.operation_counter[this.user_id];
|
if (this.operation_counter[this.user_id] != null) {
|
||||||
delete this.operation_counter[this.user_id];
|
this.operation_counter[id] = this.operation_counter[this.user_id];
|
||||||
|
delete this.operation_counter[this.user_id];
|
||||||
|
}
|
||||||
return this.user_id = id;
|
return this.user_id = id;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -221,7 +223,7 @@ HistoryBuffer = (function() {
|
|||||||
_results = [];
|
_results = [];
|
||||||
for (user in state_vector) {
|
for (user in state_vector) {
|
||||||
state = state_vector[user];
|
state = state_vector[user];
|
||||||
if ((this.operation_counter[user] == null) || (this.operation_counter[user] < state_vector[user])) {
|
if (((this.operation_counter[user] == null) || (this.operation_counter[user] < state_vector[user])) && (state_vector[user] != null)) {
|
||||||
_results.push(this.operation_counter[user] = state_vector[user]);
|
_results.push(this.operation_counter[user] = state_vector[user]);
|
||||||
} else {
|
} else {
|
||||||
_results.push(void 0);
|
_results.push(void 0);
|
||||||
|
@ -187,7 +187,7 @@ module.exports = function(HB) {
|
|||||||
|
|
||||||
ReplaceManager.prototype.callEventDecorator = function(events) {
|
ReplaceManager.prototype.callEventDecorator = function(events) {
|
||||||
var event, name, prop, _i, _len, _ref;
|
var event, name, prop, _i, _len, _ref;
|
||||||
if (!this.isDeleted()) {
|
if (!(this.isDeleted() || this.getLastOperation().isDeleted())) {
|
||||||
for (_i = 0, _len = events.length; _i < _len; _i++) {
|
for (_i = 0, _len = events.length; _i < _len; _i++) {
|
||||||
event = events[_i];
|
event = events[_i];
|
||||||
_ref = this.event_properties;
|
_ref = this.event_properties;
|
||||||
|
@ -13,14 +13,14 @@ adaptConnector = require("./ConnectorAdapter");
|
|||||||
createY = function(connector) {
|
createY = function(connector) {
|
||||||
var HB, Y, type_manager, types, user_id;
|
var HB, Y, type_manager, types, user_id;
|
||||||
user_id = null;
|
user_id = null;
|
||||||
if (connector.id != null) {
|
if (connector.user_id != null) {
|
||||||
user_id = connector.id;
|
user_id = connector.user_id;
|
||||||
} else {
|
} else {
|
||||||
user_id = "_temp";
|
user_id = "_temp";
|
||||||
connector.onUserIdSet(function(id) {
|
connector.on_user_id_set = function(id) {
|
||||||
user_id = id;
|
user_id = id;
|
||||||
return HB.resetUserId(id);
|
return HB.resetUserId(id);
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
HB = new HistoryBuffer(user_id);
|
HB = new HistoryBuffer(user_id);
|
||||||
type_manager = json_types_uninitialized(HB);
|
type_manager = json_types_uninitialized(HB);
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -10,6 +10,5 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<y-test></y-test>
|
<y-test></y-test>
|
||||||
<script src="./index.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
setTimeout(function(){
|
|
||||||
window.y_test = document.querySelector("y-test");
|
|
||||||
|
|
||||||
window.y_test.y.val("stuff",{otherstuff:{nostuff:"this is no stuff"}})
|
|
||||||
setTimeout(function(){
|
|
||||||
var res = y_test.y.val("stuff");
|
|
||||||
if(!(y_test.nostuff === "this is no stuff")){
|
|
||||||
console.log("Deep inherit doesn't work!")
|
|
||||||
}
|
|
||||||
window.y_stuff_property.val = {nostuff: "this is also no stuff"};
|
|
||||||
setTimeout(function(){
|
|
||||||
if(!(y_test.nostuff === "this is also no stuff")){
|
|
||||||
console.log("Element val overwrite doesn't work")
|
|
||||||
}
|
|
||||||
console.log("Everything is fine :)");
|
|
||||||
},500)
|
|
||||||
},500);
|
|
||||||
},3000)
|
|
@ -1,11 +1,11 @@
|
|||||||
<link rel="import" href="../../y-object.html">
|
<link rel="import" href="../../y-object.html">
|
||||||
<link rel="import" href="../../../y-connectors/y-xmpp/y-xmpp.html">
|
<link rel="import" href="../../../y-xmpp/build/browser/y-xmpp.html">
|
||||||
<link rel="import" href="../../../paper-slider/paper-slider.html">
|
<link rel="import" href="../../../paper-slider/paper-slider.html">
|
||||||
|
|
||||||
<polymer-element name="y-test" attributes="y connector stuff">
|
<polymer-element name="y-test" attributes="y connector stuff">
|
||||||
<template>
|
<template>
|
||||||
<h1 id="text" contentEditable> Check this out !</h1>
|
<h1 id="text" contentEditable> Check this out !</h1>
|
||||||
<y-xmpp id="connector" connector={{connector}} room="testy-xmpp-polymer"></y-xmpp>
|
<y-xmpp id="connector" connector={{connector}} room="testy-xmpp-polymer" syncMode="syncAll"></y-xmpp>
|
||||||
<y-object connector={{connector}} val={{y}}>
|
<y-object connector={{connector}} val={{y}}>
|
||||||
<y-property name="slider" val={{slider}}>
|
<y-property name="slider" val={{slider}}>
|
||||||
</y-property>
|
</y-property>
|
||||||
@ -32,6 +32,23 @@
|
|||||||
}
|
}
|
||||||
that.y.val("text").bind(that.$.text,that.shadowRoot)
|
that.y.val("text").bind(that.$.text,that.shadowRoot)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Everything is initialized. Lets test stuff!
|
||||||
|
window.y_test = this;
|
||||||
|
window.y_test.y.val("stuff",{otherstuff:{nostuff:"this is no stuff"}})
|
||||||
|
setTimeout(function(){
|
||||||
|
var res = y_test.y.val("stuff");
|
||||||
|
if(!(y_test.nostuff === "this is no stuff")){
|
||||||
|
console.log("Deep inherit doesn't work!")
|
||||||
|
}
|
||||||
|
window.y_stuff_property.val = {nostuff: "this is also no stuff"};
|
||||||
|
setTimeout(function(){
|
||||||
|
if(!(y_test.nostuff === "this is also no stuff")){
|
||||||
|
console.log("Element val overwrite doesn't work")
|
||||||
|
}
|
||||||
|
console.log("Everything is fine :)");
|
||||||
|
},500)
|
||||||
|
},500);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<meta charset=utf-8 />
|
<meta charset=utf-8 />
|
||||||
<title>Y Example</title>
|
<title>Y Example</title>
|
||||||
<script src="../../build/browser/y.js"></script>
|
<script src="../../build/browser/y.js"></script>
|
||||||
<script src="../../../y-connectors/y-xmpp/y-xmpp.js"></script>
|
<script src="../../../y-xmpp/y-xmpp.js"></script>
|
||||||
<script src="./index.js"></script>
|
<script src="./index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
connector = new Y.XMPP("testy-xmpp-json2");
|
connector = new Y.XMPP().join("testy-xmpp-json3", {syncMode: "syncAll"});
|
||||||
connector.debug = true
|
connector.debug = true
|
||||||
|
|
||||||
y = new Y(connector);
|
y = new Y(connector);
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
|
||||||
|
ConnectorClass = require "./ConnectorClass"
|
||||||
#
|
#
|
||||||
# @param {Engine} engine The transformation engine
|
# @param {Engine} engine The transformation engine
|
||||||
# @param {HistoryBuffer} HB
|
# @param {HistoryBuffer} HB
|
||||||
# @param {Array<Function>} execution_listener You must ensure that whenever an operation is executed, every function in this Array is called.
|
# @param {Array<Function>} execution_listener You must ensure that whenever an operation is executed, every function in this Array is called.
|
||||||
#
|
#
|
||||||
adaptConnector = (connector, engine, HB, execution_listener)->
|
adaptConnector = (connector, engine, HB, execution_listener)->
|
||||||
|
|
||||||
|
for name, f of ConnectorClass
|
||||||
|
connector[name] = f
|
||||||
|
|
||||||
send_ = (o)->
|
send_ = (o)->
|
||||||
if o.uid.creator is HB.getUserId() and (typeof o.uid.op_number isnt "string")
|
if o.uid.creator is HB.getUserId() and (typeof o.uid.op_number isnt "string")
|
||||||
connector.broadcast o
|
connector.broadcast o
|
||||||
@ -46,6 +50,7 @@ adaptConnector = (connector, engine, HB, execution_listener)->
|
|||||||
connector.getHB = getHB
|
connector.getHB = getHB
|
||||||
connector.applyHB = applyHB
|
connector.applyHB = applyHB
|
||||||
|
|
||||||
|
connector.receive_handlers = []
|
||||||
connector.receive_handlers.push (sender, op)->
|
connector.receive_handlers.push (sender, op)->
|
||||||
if op.uid.creator isnt HB.getUserId()
|
if op.uid.creator isnt HB.getUserId()
|
||||||
engine.applyOp op
|
engine.applyOp op
|
||||||
|
279
lib/ConnectorClass.coffee
Normal file
279
lib/ConnectorClass.coffee
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
|
||||||
|
module.exports =
|
||||||
|
#
|
||||||
|
# @params new Connector(syncMode, role)
|
||||||
|
# @param syncMode {String} is either "syncAll" or "master-slave".
|
||||||
|
# @param role {String} The role of this client
|
||||||
|
# (slave or master (only used when syncMode is master-slave))
|
||||||
|
#
|
||||||
|
init: (options)->
|
||||||
|
req = (name, choices)=>
|
||||||
|
if options[name]?
|
||||||
|
if (not choices?) or choices.some((c)->c is options[name])
|
||||||
|
@[name] = options[name]
|
||||||
|
else
|
||||||
|
throw new Error "You can set the '"+name+"' option to one of the following choices: "+JSON.encode(choices)
|
||||||
|
else
|
||||||
|
throw new Error "You must specify "+name+", when initializing the Connector!"
|
||||||
|
|
||||||
|
req "syncMode", ["syncAll", "master-slave"]
|
||||||
|
req "role", ["master", "slave"]
|
||||||
|
req "user_id"
|
||||||
|
@on_user_id_set(@user_id)
|
||||||
|
|
||||||
|
# A Master should sync with everyone! TODO: really? - for now its safer this way!
|
||||||
|
if @role is "master"
|
||||||
|
@syncMode = "syncAll"
|
||||||
|
|
||||||
|
# is set to true when this is synced with all other connections
|
||||||
|
@is_synced = false
|
||||||
|
# true, iff the client is currently syncing
|
||||||
|
@is_syncing = false
|
||||||
|
# Peerjs Connections: key: conn-id, value: object
|
||||||
|
@connections = {}
|
||||||
|
# List of functions that shall process incoming data
|
||||||
|
# @receive_handlers = [] # this is already set in the ConnectorAdapter!
|
||||||
|
|
||||||
|
# whether this instance is bound to any y instance
|
||||||
|
@is_bound_to_y = false
|
||||||
|
@connections = {}
|
||||||
|
@current_sync_target = null
|
||||||
|
|
||||||
|
isRoleMaster: ->
|
||||||
|
@role is "master"
|
||||||
|
|
||||||
|
isRoleSlave: ->
|
||||||
|
@role is "slave"
|
||||||
|
|
||||||
|
findNewSyncTarget: ()->
|
||||||
|
@current_sync_target = null
|
||||||
|
if @syncMode is "syncAll"
|
||||||
|
for user, c of @connections
|
||||||
|
if not c.is_synced
|
||||||
|
@performSync user
|
||||||
|
break
|
||||||
|
null
|
||||||
|
|
||||||
|
userLeft: (user)->
|
||||||
|
delete @connections[user]
|
||||||
|
@findNewSyncTarget()
|
||||||
|
|
||||||
|
userJoined: (user, role)->
|
||||||
|
if not role?
|
||||||
|
throw new Error "Internal: You must specify the role of the joined user!"
|
||||||
|
# a user joined the room
|
||||||
|
@connections[user] =
|
||||||
|
is_synced : false
|
||||||
|
|
||||||
|
if (not @is_synced) or @syncMode is "syncAll"
|
||||||
|
if @syncMode is "syncAll"
|
||||||
|
@performSync user
|
||||||
|
else if role is "master"
|
||||||
|
# TODO: What if there are two masters? Prevent sending everything two times!
|
||||||
|
@performSyncWithMaster user
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Execute a function _when_ we are connected. If not connected, wait until connected.
|
||||||
|
# @param f {Function} Will be executed on the PeerJs-Connector context.
|
||||||
|
#
|
||||||
|
whenSynced: (args)->
|
||||||
|
if args.constructore is Function
|
||||||
|
args = [args]
|
||||||
|
if @is_synced
|
||||||
|
args[0].apply this, args[1..]
|
||||||
|
else
|
||||||
|
@compute_when_synced ?= []
|
||||||
|
@compute_when_synced.push args
|
||||||
|
|
||||||
|
#
|
||||||
|
# Execute an function when a message is received.
|
||||||
|
# @param f {Function} Will be executed on the PeerJs-Connector context. f will be called with (sender_id, broadcast {true|false}, message).
|
||||||
|
#
|
||||||
|
onReceive: (f)->
|
||||||
|
@receive_handlers.push f
|
||||||
|
|
||||||
|
###
|
||||||
|
# Broadcast a message to all connected peers.
|
||||||
|
# @param message {Object} The message to broadcast.
|
||||||
|
#
|
||||||
|
broadcast: (message)->
|
||||||
|
throw new Error "You must implement broadcast!"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Send a message to a peer, or set of peers
|
||||||
|
#
|
||||||
|
send: (peer_s, message)->
|
||||||
|
throw new Error "You must implement send!"
|
||||||
|
###
|
||||||
|
|
||||||
|
#
|
||||||
|
# perform a sync with a specific user.
|
||||||
|
#
|
||||||
|
performSync: (user)->
|
||||||
|
if not @current_sync_target?
|
||||||
|
@current_sync_target = user
|
||||||
|
@send user,
|
||||||
|
sync_step: "getHB"
|
||||||
|
data: @getStateVector()
|
||||||
|
|
||||||
|
#
|
||||||
|
# When a master node joined the room, perform this sync with him. It will ask the master for the HB,
|
||||||
|
# and will broadcast his own HB
|
||||||
|
#
|
||||||
|
performSyncWithMaster: (user)->
|
||||||
|
if not @is_syncing
|
||||||
|
@current_sync_target = user
|
||||||
|
@is_syncing = true
|
||||||
|
@send user,
|
||||||
|
sync_step: "getHB"
|
||||||
|
send_again: "true"
|
||||||
|
data: []
|
||||||
|
hb = @getHB([]).hb
|
||||||
|
_hb = []
|
||||||
|
for o in hb
|
||||||
|
_hb.push o
|
||||||
|
if _hb.length > 30
|
||||||
|
@broadcast
|
||||||
|
sync_step: "applyHB_"
|
||||||
|
data: _hb
|
||||||
|
_hb = []
|
||||||
|
@broadcast
|
||||||
|
sync_step: "applyHB"
|
||||||
|
data: _hb
|
||||||
|
#
|
||||||
|
# You are sure that all clients are synced, call this function.
|
||||||
|
#
|
||||||
|
setStateSynced: ()->
|
||||||
|
if not @is_synced
|
||||||
|
@is_synced = true
|
||||||
|
for f in @compute_when_synced
|
||||||
|
f()
|
||||||
|
delete @compute_when_synced
|
||||||
|
null
|
||||||
|
|
||||||
|
#
|
||||||
|
# You received a raw message, and you know that it is intended for to Yjs. Then call this function.
|
||||||
|
#
|
||||||
|
receiveMessage: (sender, res)->
|
||||||
|
if not res.sync_step?
|
||||||
|
for f in @receive_handlers
|
||||||
|
f sender, res
|
||||||
|
else
|
||||||
|
if res.sync_step is "getHB"
|
||||||
|
data = @getHB(res.data)
|
||||||
|
hb = data.hb
|
||||||
|
_hb = []
|
||||||
|
if @is_synced
|
||||||
|
sendApplyHB = ()->
|
||||||
|
|
||||||
|
for o in hb
|
||||||
|
_hb.push o
|
||||||
|
if _hb.length > 30
|
||||||
|
@send sender,
|
||||||
|
sync_step: "applyHB_"
|
||||||
|
data: _hb
|
||||||
|
_hb = []
|
||||||
|
if @is_synced
|
||||||
|
@send sender,
|
||||||
|
sync_s tep: "applyHB"
|
||||||
|
data: _hb
|
||||||
|
|
||||||
|
if res.send_again?
|
||||||
|
send_again = do (sv = data.state_vector)=>
|
||||||
|
()=>
|
||||||
|
hb = @getHB(sv).hb
|
||||||
|
@send sender,
|
||||||
|
sync_step: "applyHB",
|
||||||
|
data: hb
|
||||||
|
sent_again: "true"
|
||||||
|
setTimeout send_again, 3000
|
||||||
|
else if res.sync_step is "applyHB"
|
||||||
|
@applyHB(res.data)
|
||||||
|
|
||||||
|
if (@syncMode is "syncAll" or res.sent_again?) and not @is_synced
|
||||||
|
@setStateSynced()
|
||||||
|
@connections[sender].is_synced = true
|
||||||
|
@findNewSyncTarget()
|
||||||
|
|
||||||
|
else if res.sync_step is "applyHB_"
|
||||||
|
@applyHB(res.data)
|
||||||
|
|
||||||
|
|
||||||
|
# Currently, the HB encodes operations as JSON. For the moment I want to keep it
|
||||||
|
# that way. Maybe we support encoding in the HB as XML in the future, but for now I don't want
|
||||||
|
# too much overhead. Y is very likely to get changed a lot in the future
|
||||||
|
#
|
||||||
|
# Because we don't want to encode JSON as string (with character escaping, wich makes it pretty much unreadable)
|
||||||
|
# we encode the JSON as XML.
|
||||||
|
#
|
||||||
|
# When the HB support encoding as XML, the format should look pretty much like this.
|
||||||
|
|
||||||
|
# does not support primitive values as array elements
|
||||||
|
# expects an ltx (less than xml) object
|
||||||
|
parseMessageFromXml: (m)->
|
||||||
|
parse_array = (node)->
|
||||||
|
for n in node.children
|
||||||
|
if n.getAttribute("isArray") is "true"
|
||||||
|
parse_array n
|
||||||
|
else
|
||||||
|
parse_object n
|
||||||
|
|
||||||
|
parse_object = (node)->
|
||||||
|
json = {}
|
||||||
|
for name, value of node.attrs
|
||||||
|
int = parseInt(value)
|
||||||
|
if isNaN(int) or (""+int) isnt value
|
||||||
|
json[name] = value
|
||||||
|
else
|
||||||
|
json[name] = int
|
||||||
|
for n in node.children
|
||||||
|
name = n.name
|
||||||
|
if n.getAttribute("isArray") is "true"
|
||||||
|
json[name] = parse_array n
|
||||||
|
else
|
||||||
|
json[name] = parse_object n
|
||||||
|
json
|
||||||
|
parse_object m
|
||||||
|
|
||||||
|
# encode message in xml
|
||||||
|
# we use string because Strophe only accepts an "xml-string"..
|
||||||
|
# So {a:4,b:{c:5}} will look like
|
||||||
|
# <y a="4">
|
||||||
|
# <b c="5"></b>
|
||||||
|
# </y>
|
||||||
|
# m - ltx element
|
||||||
|
# json - guess it ;)
|
||||||
|
#
|
||||||
|
encodeMessageToXml: (m, json)->
|
||||||
|
# attributes is optional
|
||||||
|
encode_object = (m, json)->
|
||||||
|
for name,value of json
|
||||||
|
if not value?
|
||||||
|
# nop
|
||||||
|
else if value.constructor is Object
|
||||||
|
encode_object m.c(name), value
|
||||||
|
else if value.constructor is Array
|
||||||
|
encode_array m.c(name), value
|
||||||
|
else
|
||||||
|
m.setAttribute(name,value)
|
||||||
|
m
|
||||||
|
encode_array = (m, array)->
|
||||||
|
m.setAttribute("isArray","true")
|
||||||
|
for e in array
|
||||||
|
if e.constructor is Object
|
||||||
|
encode_object m.c("array-element"), e
|
||||||
|
else
|
||||||
|
encode_array m.c("array-element"), e
|
||||||
|
m
|
||||||
|
if json.constructor is Object
|
||||||
|
encode_object m.c("y",{xmlns:"http://y.ninja/connector-stanza"}), json
|
||||||
|
else if json.constructor is Array
|
||||||
|
encode_array m.c("y",{xmlns:"http://y.ninja/connector-stanza"}), json
|
||||||
|
else
|
||||||
|
throw new Error "I can't encode this json!"
|
||||||
|
|
||||||
|
setIsBoundToY: ()->
|
||||||
|
@on_bound_to_y?()
|
||||||
|
delete @when_bound_to_y
|
||||||
|
@is_bound_to_y = true
|
@ -31,9 +31,9 @@ class HistoryBuffer
|
|||||||
throw new Error "You are re-assigning an old user id - this is not (yet) possible!"
|
throw new Error "You are re-assigning an old user id - this is not (yet) possible!"
|
||||||
@buffer[id] = own
|
@buffer[id] = own
|
||||||
delete @buffer[@user_id]
|
delete @buffer[@user_id]
|
||||||
|
if @operation_counter[@user_id]?
|
||||||
@operation_counter[id] = @operation_counter[@user_id]
|
@operation_counter[id] = @operation_counter[@user_id]
|
||||||
delete @operation_counter[@user_id]
|
delete @operation_counter[@user_id]
|
||||||
@user_id = id
|
@user_id = id
|
||||||
|
|
||||||
emptyGarbage: ()=>
|
emptyGarbage: ()=>
|
||||||
@ -196,7 +196,7 @@ class HistoryBuffer
|
|||||||
# you renew your own state_vector to the state_vector of the other user
|
# you renew your own state_vector to the state_vector of the other user
|
||||||
renewStateVector: (state_vector)->
|
renewStateVector: (state_vector)->
|
||||||
for user,state of state_vector
|
for user,state of state_vector
|
||||||
if (not @operation_counter[user]?) or (@operation_counter[user] < state_vector[user])
|
if ((not @operation_counter[user]?) or (@operation_counter[user] < state_vector[user])) and state_vector[user]?
|
||||||
@operation_counter[user] = state_vector[user]
|
@operation_counter[user] = state_vector[user]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -184,7 +184,7 @@ module.exports = (HB)->
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
callEventDecorator: (events)->
|
callEventDecorator: (events)->
|
||||||
if not @isDeleted()
|
if not (@isDeleted() or @getLastOperation().isDeleted())
|
||||||
for event in events
|
for event in events
|
||||||
for name,prop of @event_properties
|
for name,prop of @event_properties
|
||||||
event[name] = prop
|
event[name] = prop
|
||||||
|
@ -6,11 +6,11 @@ adaptConnector = require "./ConnectorAdapter"
|
|||||||
|
|
||||||
createY = (connector)->
|
createY = (connector)->
|
||||||
user_id = null
|
user_id = null
|
||||||
if connector.id?
|
if connector.user_id?
|
||||||
user_id = connector.id # TODO: change to getUniqueId()
|
user_id = connector.user_id # TODO: change to getUniqueId()
|
||||||
else
|
else
|
||||||
user_id = "_temp"
|
user_id = "_temp"
|
||||||
connector.onUserIdSet (id)->
|
connector.on_user_id_set = (id)->
|
||||||
user_id = id
|
user_id = id
|
||||||
HB.resetUserId id
|
HB.resetUserId id
|
||||||
HB = new HistoryBuffer user_id
|
HB = new HistoryBuffer user_id
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
<link rel="import" href="../polymer/polymer.html">
|
||||||
|
|
||||||
<polymer-element name="y-object" hidden attributes="val connector y">
|
<polymer-element name="y-object" hidden attributes="val connector y">
|
||||||
</polymer-element>
|
</polymer-element>
|
||||||
<polymer-element name="y-property" hidden attributes="val name y">
|
<polymer-element name="y-property" hidden attributes="val name y">
|
||||||
</polymer-element>
|
</polymer-element>
|
||||||
|
|
||||||
<script src="./build/browser/y-object.js"></script>
|
<script src="./y-object.js"></script>
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user