From b60df56157ee1fd0bd4938799bac05a62fda91a1 Mon Sep 17 00:00:00 2001 From: lookshe Date: Sat, 14 Mar 2015 20:45:20 +0100 Subject: initial commit from working version --- client-website/js/symple.client.js | 544 +++++++++++++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 client-website/js/symple.client.js (limited to 'client-website/js/symple.client.js') 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; + } +}; -- cgit v1.2.3