aboutsummaryrefslogtreecommitdiffstats
path: root/client-website/js/symple.client.js
diff options
context:
space:
mode:
Diffstat (limited to 'client-website/js/symple.client.js')
-rw-r--r--client-website/js/symple.client.js544
1 files changed, 544 insertions, 0 deletions
diff --git a/client-website/js/symple.client.js b/client-website/js/symple.client.js
new file mode 100644
index 0000000..37a3e38
--- /dev/null
+++ b/client-website/js/symple.client.js
@@ -0,0 +1,544 @@
+// -----------------------------------------------------------------------------
+// Symple Client
+//
+Symple.Client = Symple.Dispatcher.extend({
+ init: function(options) {
+ this.options = $.extend({
+ url: options.url ? options.url : 'http://localhost:4000',
+ secure: options.url && (
+ options.url.indexOf('https') == 0 ||
+ options.url.indexOf('wss') == 0) ? true : false,
+ token: undefined // pre-arranged server session token
+ //timeout: 0 // set for connection timeout
+ }, options);
+ this._super();
+ this.peer = options.peer || {};
+ this.roster = new Symple.Roster(this);
+ this.socket = null;
+ },
+
+ disconnect: function() {
+ this.socket.disconnect();
+ },
+
+ // Connects and authenticates on the server.
+ // If the server is down the 'error' event will fire.
+ connect: function() {
+ Symple.log('Symple Client: Connecting: ', this.options);
+ self = this;
+ if (this.socket)
+ throw 'The client socket is not null'
+ this.socket = io.connect(this.options.url, this.options);
+ this.socket.on('connect', function() {
+ Symple.log('Symple Client: Connected');
+ self.socket.emit('announce', {
+ token: self.options.token || "",
+ group: self.peer.group || "",
+ user: self.peer.user || "",
+ name: self.peer.name || "",
+ type: self.peer.type || ""
+ }, function(res) {
+ Symple.log('Symple Client: Announced: ', res);
+ if (res.status != 200) {
+ self.setError('auth', res);
+ return;
+ }
+ self.peer = $.extend(self.peer, res.data);
+ self.roster.add(res.data);
+ self.sendPresence({ probe: true });
+ self.doDispatch('announce', res);
+ self.socket.on('message', function(m) {
+ //Symple.log('Symple Client: Receive: ', m);
+ if (typeof(m) == 'object') {
+ switch(m.type) {
+ case 'message':
+ m = new Symple.Message(m);
+ break;
+ case 'command':
+ m = new Symple.Command(m);
+ break;
+ case 'event':
+ m = new Symple.Event(m);
+ break;
+ case 'presence':
+ m = new Symple.Presence(m);
+ if (m.data.online)
+ self.roster.update(m.data);
+ else
+ self.roster.remove(m.data.id);
+ if (m.probe)
+ self.sendPresence(new Symple.Presence({ to: m.from }));
+ break;
+ default:
+ o = m;
+ o.type = o.type || 'message';
+ break;
+ }
+
+ if (typeof(m.from) != 'string') {
+ Symple.log('Symple Client: Invalid sender address: ', m);
+ return;
+ }
+
+ // Replace the from attribute with the full peer object.
+ // This will only work for peer messages, not server messages.
+ var rpeer = self.roster.get(m.from);
+ if (rpeer)
+ m.from = rpeer;
+ else
+ Symple.log('Symple Client: Got message from unknown peer: ', m);
+
+ // Dispatch to the application
+ self.doDispatch(m.type, m);
+ }
+ });
+ });
+ });
+ this.socket.on('error', function() {
+ // This is triggered when any transport fails,
+ // so not necessarily fatal
+ //self.setError('connect');
+ self.doDispatch('connect');
+ });
+ this.socket.on('connecting', function() {
+ Symple.log('Symple Client: Connecting');
+ self.doDispatch('connecting');
+ });
+ this.socket.on('reconnecting', function() {
+ Symple.log('Symple Client: Reconnecting');
+ self.doDispatch('reconnecting');
+ });
+ this.socket.on('connect_failed', function() {
+ // Called when all transports fail
+ Symple.log('Symple Client: Connect failed');
+ self.doDispatch('connect_failed');
+ self.setError('connect');
+ });
+ this.socket.on('disconnect', function() {
+ Symple.log('Symple Client: Disconnect');
+ self.peer.online = false;
+ self.doDispatch('disconnect');
+ });
+ },
+
+ online: function() {
+ return this.peer.online;
+ },
+
+ getPeers: function(fn) {
+ self = this;
+ this.socket.emit('peers', function(res) {
+ Symple.log('Peers: ', res);
+ if (typeof(res) != 'object')
+ for (var peer in res)
+ self.roster.update(peer);
+ if (fn)
+ fn(res);
+ });
+ },
+
+ send: function(m) {
+ //Symple.log('Symple Client: Sending: ', m);
+ if (!this.online()) throw 'Cannot send messages when offline';
+ if (typeof(m) != 'object') throw 'Message must be an object';
+ if (typeof(m.type) != 'string') m.type = 'message' //throw 'Message must have a type attribute';
+ if (!m.id) m.id = Symple.randomString(8);
+ if (m.to && typeof(m.to) == 'object' && (m.to.group || m.to.user || m.to.id))
+ m.to = Symple.buildAddress(m.to, this.peer.group);
+ if (m.to && typeof(m.to) != 'string')
+ throw 'Message \'to\' attribute must be an address string';
+ if (m.to && m.to.indexOf(this.peer.id) != -1)
+ throw 'Message sender cannot match the recipient';
+ //if (typeof(m.to) == 'object' && m.to && m.to.id == m.from.id)
+ // throw 'The sender must not match the recipient';
+ m.from = Symple.buildAddress(this.peer);
+ Symple.log('Symple Client: Sending: ', m);
+ this.socket.json.send(m);
+ },
+
+ respond: function(m) {
+ m.to = m.from;
+ this.send(m);
+ },
+
+ sendMessage: function(m) { //, fn
+ this.send(m); //, fn
+ },
+
+ sendPresence: function(p) {
+ p = p || {};
+ if (!this.online()) throw 'Cannot send message while offline';
+ if (p.data) {
+ p.data = Symple.merge(this.peer, p.data);
+ }
+ else
+ p.data = this.peer;
+ Symple.log('Symple Client: Sending Presence: ', p);
+ this.send(new Symple.Presence(p));
+ },
+
+ sendCommand: function(c, fn, once) {
+ var self = this;
+ c = new Symple.Command(c);
+ this.send(c);
+ if (fn) {
+ this.onResponse('command', {
+ id: c.id
+ }, fn, function(res) {
+ if (once || (
+ // 202 (Accepted) and 406 (Not acceptable) response codes
+ // signal that the command has not yet completed.
+ res.status != 202 &&
+ res.status != 406)) {
+ self.clear('command', fn);
+ }
+ });
+ }
+ },
+
+ // Adds a capability for our current peer
+ addCapability: function(name, value) {
+ var peer = this.peer;
+ if (peer) {
+ if (typeof value == 'undefined')
+ value = true
+ if (typeof peer.capabilities == 'undefined')
+ peer.capabilities = {}
+ peer.capabilities[name] = value;
+ //var idx = peer.capabilities.indexOf(name);
+ //if (idx == -1) {
+ // peer.capabilities.push(name);
+ // this.sendPresence();
+ //}
+ }
+ },
+
+ // Removes a capability from our current peer
+ removeCapability: function(name) {
+ var peer = this.peer;
+ if (peer && typeof peer.capabilities != 'undefined' &&
+ typeof peer.capabilities[name] != 'undefined') {
+ delete peer.capabilities[key];
+ this.sendPresence();
+ //var idx = peer.capabilities.indexOf(name)
+ //if (idx != -1) {
+ // peer.capabilities.pop(name);
+ // this.sendPresence();
+ //}
+ }
+ },
+
+ // Checks if a peer has a specific capbility and returns a boolean
+ hasCapability: function(id, name) {
+ var peer = this.roster.get(id)
+ if (peer) {
+ if (typeof peer.capabilities != 'undefined' &&
+ typeof peer.capabilities[name] != 'undefined')
+ return peer.capabilities[name] !== false;
+ if (typeof peer.data != 'undefined' &&
+ typeof peer.data.capabilities != 'undefined' &&
+ typeof peer.data.capabilities[name] != 'undefined')
+ return peer.data.capabilities[name] !== false;
+ }
+ return false;
+ },
+
+ // Checks if a peer has a specific capbility and returns the value
+ getCapability: function(id, name) {
+ var peer = this.roster.get(id)
+ if (peer) {
+ if (typeof peer.capabilities != 'undefined' &&
+ typeof peer.capabilities[name] != 'undefined')
+ return peer.capabilities[name];
+ if (typeof peer.data != 'undefined' &&
+ typeof peer.data.capabilities != 'undefined' &&
+ typeof peer.data.capabilities[name] != 'undefined')
+ return peer.data.capabilities[name];
+ }
+ return undefined;
+ },
+
+ // Sets the client to an error state and disconnect
+ setError: function(error, message) {
+ Symple.log('Symple Client: Client error: ', error, message);
+ //if (this.error == error)
+ // return;
+ //this.error = error;
+ this.doDispatch('error', error, message);
+ if (this.socket)
+ this.socket.disconnect();
+ },
+
+ onResponse: function(event, filters, fn, after) {
+ if (typeof this.listeners[event] == 'undefined')
+ this.listeners[event] = [];
+ if (typeof fn != 'undefined' && fn.constructor == Function)
+ this.listeners[event].push({
+ fn: fn, // data callback function
+ after: after, // after data callback function
+ filters: filters // event filter object for matching response
+ });
+ },
+
+ clear: function(event, fn) {
+ Symple.log('Symple Client: Clearing callback: ', event);
+ if (typeof this.listeners[event] != 'undefined') {
+ for (var i = 0; i < this.listeners[event].length; i++) {
+ if (this.listeners[event][i].fn === fn &&
+ String(this.listeners[event][i].fn) == String(fn)) {
+ this.listeners[event].splice(i, 1);
+ Symple.log('Symple Client: Clearing callback: OK: ', event);
+ }
+ }
+ }
+ },
+
+ doDispatch: function() {
+ // Modified dispatch function response callbacks first.
+ // If a match is found event propagation will be terminated.
+ if (!this.dispatchResponse.apply(this, arguments)) {
+ this.dispatch.apply(this, arguments);
+ }
+ },
+
+ dispatchResponse: function() {
+ var event = arguments[0];
+ var data = Array.prototype.slice.call(arguments, 1);
+ if (typeof this.listeners[event] != 'undefined') {
+ for (var i = 0; i < this.listeners[event].length; i++) {
+ if (typeof this.listeners[event][i] == 'object' &&
+ this.listeners[event][i].filters != 'undefined' &&
+ Symple.match(this.listeners[event][i].filters, data[0])) {
+ this.listeners[event][i].fn.apply(this, data);
+ if (this.listeners[event][i].after != 'undefined') {
+ this.listeners[event][i].after.apply(this, data);
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+});
+
+
+// -----------------------------------------------------------------------------
+// Symple Roster
+//
+Symple.Roster = Symple.Manager.extend({
+ init: function(client) {
+ Symple.log('Symple Roster: Creating');
+ this._super();
+ this.client = client;
+ },
+
+ // Add a peer object to the roster
+ add: function(peer) {
+ Symple.log('Symple Roster: Adding: ', peer);
+ if (!peer || !peer.id || !peer.user || !peer.group)
+ throw 'Cannot add invalid peer'
+ this._super(peer);
+ this.client.doDispatch('addPeer', peer);
+ },
+
+ // Remove the peer matching an ID or address string: user@group/id
+ remove: function(id) {
+ id = Symple.parseIDFromAddress(id) || id;
+ var peer = this._super(id);
+ Symple.log('Symple Roster: Removing: ', id, peer);
+ if (peer)
+ this.client.doDispatch('removePeer', peer);
+ return peer;
+ },
+
+ // Get the peer matching an ID or address string: user@group/id
+ get: function(id) {
+
+ // Handle IDs
+ peer = this._super(id); // id = Symple.parseIDFromAddress(id) || id;
+ if (peer)
+ return peer;
+
+ // Handle address strings
+ return this.findOne(Symple.parseAddress(id));
+ },
+
+ update: function(data) {
+ if (!data || !data.id)
+ return;
+ var peer = this.get(data.id);
+ if (peer)
+ for (var key in data)
+ peer[key] = data[key];
+ else
+ this.add(data);
+ }
+
+ // Get the peer matching an address string: user@group/id
+ //getForAddr: function(addr) {
+ // var o = Symple.parseAddress(addr);
+ // if (o && o.id)
+ // return this.get(o.id);
+ // return null;
+ //}
+});
+
+
+// -----------------------------------------------------------------------------
+// Helpers
+//
+Symple.parseIDFromAddress = function(str) {
+ var arr = str.split("/")
+ if (arr.length == 2)
+ return arr[1];
+ return null;
+};
+
+Symple.parseAddress = function(str) {
+ var addr = {}, base,
+ arr = str.split("/")
+
+ if (arr.length < 2) // no id
+ base = str;
+ else { // has id
+ addr.id = arr[1];
+ base = arr[0];
+ }
+
+ arr = base.split("@")
+ if (arr.length < 2) // group only
+ addr.group = base;
+ else { // group and user
+ addr.user = arr[0];
+ addr.group = arr[1];
+ }
+
+ return addr;
+}
+
+Symple.buildAddress = function(peer, defaultGroup) {
+ return (peer.user ? (peer.user + '@') : '') +
+ (peer.group ? peer.group : defaultGroup) + '/' +
+ (peer.id ? peer.id : '');
+}
+
+
+// -----------------------------------------------------------------------------
+// Message
+//
+Symple.Message = function(json) {
+ if (typeof(json) == 'object')
+ this.fromJSON(json);
+ this.type = "message";
+}
+
+Symple.Message.prototype = {
+ fromJSON: function(json) {
+ for (var key in json)
+ this[key] = json[key];
+ },
+
+ valid: function() {
+ return this['id']
+ && this['from'];
+ }
+};
+
+
+// -----------------------------------------------------------------------------
+// Command
+//
+Symple.Command = function(json) {
+ if (typeof(json) == 'object')
+ this.fromJSON(json);
+ this.type = "command";
+}
+
+Symple.Command.prototype = {
+ getData: function(name) {
+ return this['data'] ? this['data'][name] : null;
+ },
+
+ params: function() {
+ return this['node'].split(':');
+ },
+
+ param: function(n) {
+ return this.params()[n-1];
+ },
+
+ matches: function(xuser) {
+ xparams = xuser.split(':');
+
+ // No match if x params are greater than ours.
+ if (xparams.length > this.params().length)
+ return false;
+
+ for (var i = 0; i < xparams.length; i++) {
+
+ // Wildcard * matches everything until next parameter.
+ if (xparams[i] == "*")
+ continue;
+ if (xparams[i] != this.params()[i])
+ return false;
+ }
+
+ return true;
+ },
+
+ fromJSON: function(json) {
+ for (var key in json)
+ this[key] = json[key];
+ },
+
+ valid: function() {
+ return this['id']
+ && this['from']
+ && this['node'];
+ }
+};
+
+
+// -----------------------------------------------------------------------------
+// Presence
+//
+Symple.Presence = function(json) {
+ if (typeof(json) == 'object')
+ this.fromJSON(json);
+ this.type = "presence";
+}
+
+Symple.Presence.prototype = {
+ fromJSON: function(json) {
+ for (var key in json)
+ this[key] = json[key];
+ },
+
+ valid: function() {
+ return this['id']
+ && this['from'];
+ }
+};
+
+
+// -----------------------------------------------------------------------------
+// Event
+//
+Symple.Event = function(json) {
+ if (typeof(json) == 'object')
+ this.fromJSON(json);
+ this.type = "event";
+}
+
+Symple.Event.prototype = {
+ fromJSON: function(json) {
+ for (var key in json)
+ this[key] = json[key];
+ },
+
+ valid: function() {
+ return this['id']
+ && this['from']
+ && this.name;
+ }
+};