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("syncMethod", ["syncAll", "master-slave"]); req("role", ["master", "slave"]); req("user_id"); if (typeof this.on_user_id_set === "function") { this.on_user_id_set(this.user_id); } if (options.perform_send_again != null) { this.perform_send_again = options.perform_send_again; } else { this.perform_send_again = true; } if (this.role === "master") { this.syncMethod = "syncAll"; } this.is_synced = false; this.connections = {}; if (this.receive_handlers == null) { this.receive_handlers = []; } this.connections = {}; this.current_sync_target = null; this.sent_hb_to_all_users = false; return this.is_initialized = true; }, onUserEvent: function(f) { if (this.connections_listeners == null) { this.connections_listeners = []; } return this.connections_listeners.push(f); }, isRoleMaster: function() { return this.role === "master"; }, isRoleSlave: function() { return this.role === "slave"; }, findNewSyncTarget: function() { var c, ref, user; this.current_sync_target = null; if (this.syncMethod === "syncAll") { ref = this.connections; for (user in ref) { c = ref[user]; if (!c.is_synced) { this.performSync(user); break; } } } if (this.current_sync_target == null) { this.setStateSynced(); } return null; }, userLeft: function(user) { var f, i, len, ref, results; delete this.connections[user]; this.findNewSyncTarget(); if (this.connections_listeners != null) { ref = this.connections_listeners; results = []; for (i = 0, len = ref.length; i < len; i++) { f = ref[i]; results.push(f({ action: "userLeft", user: user })); } return results; } }, userJoined: function(user, role) { var base, f, i, len, ref, results; if (role == null) { throw new Error("Internal: You must specify the role of the joined user! E.g. userJoined('uid:3939','slave')"); } if ((base = this.connections)[user] == null) { base[user] = {}; } this.connections[user].is_synced = false; if ((!this.is_synced) || this.syncMethod === "syncAll") { if (this.syncMethod === "syncAll") { this.performSync(user); } else if (role === "master") { this.performSyncWithMaster(user); } } if (this.connections_listeners != null) { ref = this.connections_listeners; results = []; for (i = 0, len = ref.length; i < len; i++) { f = ref[i]; results.push(f({ action: "userJoined", user: user, role: role })); } return results; } }, whenSynced: function(args) { if (args.constructor === 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) { var _hb, hb, i, len, o; if (this.current_sync_target == null) { this.current_sync_target = user; this.send(user, { sync_step: "getHB", send_again: "true", data: this.getStateVector() }); if (!this.sent_hb_to_all_users) { this.sent_hb_to_all_users = true; hb = this.getHB([]).hb; _hb = []; for (i = 0, len = hb.length; i < len; i++) { o = hb[i]; _hb.push(o); if (_hb.length > 10) { this.broadcast({ sync_step: "applyHB_", data: _hb }); _hb = []; } } return this.broadcast({ sync_step: "applyHB", data: _hb }); } } }, performSyncWithMaster: function(user) { var _hb, hb, i, len, o; this.current_sync_target = user; this.send(user, { sync_step: "getHB", send_again: "true", data: this.getStateVector() }); hb = this.getHB([]).hb; _hb = []; for (i = 0, len = hb.length; i < len; i++) { o = hb[i]; _hb.push(o); if (_hb.length > 10) { this.broadcast({ sync_step: "applyHB_", data: _hb }); _hb = []; } } return this.broadcast({ sync_step: "applyHB", data: _hb }); }, setStateSynced: function() { var args, el, f, i, len, ref; if (!this.is_synced) { this.is_synced = true; if (this.compute_when_synced != null) { ref = this.compute_when_synced; for (i = 0, len = ref.length; i < len; i++) { el = ref[i]; f = el[0]; args = el.slice(1); f.apply(args); } delete this.compute_when_synced; } return null; } }, whenReceivedStateVector: function(f) { if (this.when_received_state_vector_listeners == null) { this.when_received_state_vector_listeners = []; } return this.when_received_state_vector_listeners.push(f); }, receiveMessage: function(sender, res) { var _hb, data, f, hb, i, j, k, len, len1, len2, o, ref, ref1, results, sendApplyHB, send_again; 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 (sender === this.user_id) { return; } if (res.sync_step === "getHB") { if (this.when_received_state_vector_listeners != null) { ref1 = this.when_received_state_vector_listeners; for (j = 0, len1 = ref1.length; j < len1; j++) { f = ref1[j]; f.call(this, res.data); } } delete this.when_received_state_vector_listeners; data = this.getHB(res.data); hb = data.hb; _hb = []; if (this.is_synced) { sendApplyHB = (function(_this) { return function(m) { return _this.send(sender, m); }; })(this); } else { sendApplyHB = (function(_this) { return function(m) { return _this.broadcast(m); }; })(this); } for (k = 0, len2 = hb.length; k < len2; k++) { o = hb[k]; _hb.push(o); if (_hb.length > 10) { sendApplyHB({ sync_step: "applyHB_", data: _hb }); _hb = []; } } sendApplyHB({ sync_step: "applyHB", data: _hb }); if ((res.send_again != null) && this.perform_send_again) { send_again = (function(_this) { return function(sv) { return function() { var l, len3; hb = _this.getHB(sv).hb; for (l = 0, len3 = hb.length; l < len3; l++) { o = hb[l]; _hb.push(o); if (_hb.length > 10) { _this.send(sender, { sync_step: "applyHB_", data: _hb }); _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, sender === this.current_sync_target); if ((this.syncMethod === "syncAll" || (res.sent_again != null)) && (!this.is_synced) && ((this.current_sync_target === sender) || (this.current_sync_target == null))) { this.connections[sender].is_synced = true; return this.findNewSyncTarget(); } } else if (res.sync_step === "applyHB_") { return this.applyHB(res.data, sender === this.current_sync_target); } } }, parseMessageFromXml: function(m) { var parse_array, parse_object; parse_array = function(node) { var i, len, n, 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 i, int, json, len, n, name, ref, ref1, value; 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; } };