]> defiant.homedns.org Git - ros_wild_thumper.git/blobdiff - www/assets/javascripts/roslib.js
www: Added minicolors colorpicker for lights
[ros_wild_thumper.git] / www / assets / javascripts / roslib.js
diff --git a/www/assets/javascripts/roslib.js b/www/assets/javascripts/roslib.js
new file mode 100644 (file)
index 0000000..2193da8
--- /dev/null
@@ -0,0 +1,3693 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*!\r
+ * EventEmitter2\r
+ * https://github.com/hij1nx/EventEmitter2\r
+ *\r
+ * Copyright (c) 2013 hij1nx\r
+ * Licensed under the MIT license.\r
+ */\r
+;!function(undefined) {\r
+\r
+  var isArray = Array.isArray ? Array.isArray : function _isArray(obj) {\r
+    return Object.prototype.toString.call(obj) === "[object Array]";\r
+  };\r
+  var defaultMaxListeners = 10;\r
+\r
+  function init() {\r
+    this._events = {};\r
+    if (this._conf) {\r
+      configure.call(this, this._conf);\r
+    }\r
+  }\r
+\r
+  function configure(conf) {\r
+    if (conf) {\r
+      this._conf = conf;\r
+\r
+      conf.delimiter && (this.delimiter = conf.delimiter);\r
+      this._events.maxListeners = conf.maxListeners !== undefined ? conf.maxListeners : defaultMaxListeners;\r
+      conf.wildcard && (this.wildcard = conf.wildcard);\r
+      conf.newListener && (this.newListener = conf.newListener);\r
+      conf.verboseMemoryLeak && (this.verboseMemoryLeak = conf.verboseMemoryLeak);\r
+\r
+      if (this.wildcard) {\r
+        this.listenerTree = {};\r
+      }\r
+    } else {\r
+      this._events.maxListeners = defaultMaxListeners;\r
+    }\r
+  }\r
+\r
+  function logPossibleMemoryLeak(count, eventName) {\r
+    var errorMsg = '(node) warning: possible EventEmitter memory ' +\r
+        'leak detected. %d listeners added. ' +\r
+        'Use emitter.setMaxListeners() to increase limit.';\r
+\r
+    if(this.verboseMemoryLeak){\r
+      errorMsg += ' Event name: %s.';\r
+      console.error(errorMsg, count, eventName);\r
+    } else {\r
+      console.error(errorMsg, count);\r
+    }\r
+\r
+    if (console.trace){\r
+      console.trace();\r
+    }\r
+  }\r
+\r
+  function EventEmitter(conf) {\r
+    this._events = {};\r
+    this.newListener = false;\r
+    this.verboseMemoryLeak = false;\r
+    configure.call(this, conf);\r
+  }\r
+  EventEmitter.EventEmitter2 = EventEmitter; // backwards compatibility for exporting EventEmitter property\r
+\r
+  //\r
+  // Attention, function return type now is array, always !\r
+  // It has zero elements if no any matches found and one or more\r
+  // elements (leafs) if there are matches\r
+  //\r
+  function searchListenerTree(handlers, type, tree, i) {\r
+    if (!tree) {\r
+      return [];\r
+    }\r
+    var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached,\r
+        typeLength = type.length, currentType = type[i], nextType = type[i+1];\r
+    if (i === typeLength && tree._listeners) {\r
+      //\r
+      // If at the end of the event(s) list and the tree has listeners\r
+      // invoke those listeners.\r
+      //\r
+      if (typeof tree._listeners === 'function') {\r
+        handlers && handlers.push(tree._listeners);\r
+        return [tree];\r
+      } else {\r
+        for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) {\r
+          handlers && handlers.push(tree._listeners[leaf]);\r
+        }\r
+        return [tree];\r
+      }\r
+    }\r
+\r
+    if ((currentType === '*' || currentType === '**') || tree[currentType]) {\r
+      //\r
+      // If the event emitted is '*' at this part\r
+      // or there is a concrete match at this patch\r
+      //\r
+      if (currentType === '*') {\r
+        for (branch in tree) {\r
+          if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {\r
+            listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1));\r
+          }\r
+        }\r
+        return listeners;\r
+      } else if(currentType === '**') {\r
+        endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*'));\r
+        if(endReached && tree._listeners) {\r
+          // The next element has a _listeners, add it to the handlers.\r
+          listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength));\r
+        }\r
+\r
+        for (branch in tree) {\r
+          if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {\r
+            if(branch === '*' || branch === '**') {\r
+              if(tree[branch]._listeners && !endReached) {\r
+                listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength));\r
+              }\r
+              listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));\r
+            } else if(branch === nextType) {\r
+              listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2));\r
+            } else {\r
+              // No match on this one, shift into the tree but not in the type array.\r
+              listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i));\r
+            }\r
+          }\r
+        }\r
+        return listeners;\r
+      }\r
+\r
+      listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1));\r
+    }\r
+\r
+    xTree = tree['*'];\r
+    if (xTree) {\r
+      //\r
+      // If the listener tree will allow any match for this part,\r
+      // then recursively explore all branches of the tree\r
+      //\r
+      searchListenerTree(handlers, type, xTree, i+1);\r
+    }\r
+\r
+    xxTree = tree['**'];\r
+    if(xxTree) {\r
+      if(i < typeLength) {\r
+        if(xxTree._listeners) {\r
+          // If we have a listener on a '**', it will catch all, so add its handler.\r
+          searchListenerTree(handlers, type, xxTree, typeLength);\r
+        }\r
+\r
+        // Build arrays of matching next branches and others.\r
+        for(branch in xxTree) {\r
+          if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) {\r
+            if(branch === nextType) {\r
+              // We know the next element will match, so jump twice.\r
+              searchListenerTree(handlers, type, xxTree[branch], i+2);\r
+            } else if(branch === currentType) {\r
+              // Current node matches, move into the tree.\r
+              searchListenerTree(handlers, type, xxTree[branch], i+1);\r
+            } else {\r
+              isolatedBranch = {};\r
+              isolatedBranch[branch] = xxTree[branch];\r
+              searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1);\r
+            }\r
+          }\r
+        }\r
+      } else if(xxTree._listeners) {\r
+        // We have reached the end and still on a '**'\r
+        searchListenerTree(handlers, type, xxTree, typeLength);\r
+      } else if(xxTree['*'] && xxTree['*']._listeners) {\r
+        searchListenerTree(handlers, type, xxTree['*'], typeLength);\r
+      }\r
+    }\r
+\r
+    return listeners;\r
+  }\r
+\r
+  function growListenerTree(type, listener) {\r
+\r
+    type = typeof type === 'string' ? type.split(this.delimiter) : type.slice();\r
+\r
+    //\r
+    // Looks for two consecutive '**', if so, don't add the event at all.\r
+    //\r
+    for(var i = 0, len = type.length; i+1 < len; i++) {\r
+      if(type[i] === '**' && type[i+1] === '**') {\r
+        return;\r
+      }\r
+    }\r
+\r
+    var tree = this.listenerTree;\r
+    var name = type.shift();\r
+\r
+    while (name !== undefined) {\r
+\r
+      if (!tree[name]) {\r
+        tree[name] = {};\r
+      }\r
+\r
+      tree = tree[name];\r
+\r
+      if (type.length === 0) {\r
+\r
+        if (!tree._listeners) {\r
+          tree._listeners = listener;\r
+        }\r
+        else {\r
+          if (typeof tree._listeners === 'function') {\r
+            tree._listeners = [tree._listeners];\r
+          }\r
+\r
+          tree._listeners.push(listener);\r
+\r
+          if (\r
+            !tree._listeners.warned &&\r
+            this._events.maxListeners > 0 &&\r
+            tree._listeners.length > this._events.maxListeners\r
+          ) {\r
+            tree._listeners.warned = true;\r
+            logPossibleMemoryLeak.call(this, tree._listeners.length, name);\r
+          }\r
+        }\r
+        return true;\r
+      }\r
+      name = type.shift();\r
+    }\r
+    return true;\r
+  }\r
+\r
+  // By default EventEmitters will print a warning if more than\r
+  // 10 listeners are added to it. This is a useful default which\r
+  // helps finding memory leaks.\r
+  //\r
+  // Obviously not all Emitters should be limited to 10. This function allows\r
+  // that to be increased. Set to zero for unlimited.\r
+\r
+  EventEmitter.prototype.delimiter = '.';\r
+\r
+  EventEmitter.prototype.setMaxListeners = function(n) {\r
+    if (n !== undefined) {\r
+      this._events || init.call(this);\r
+      this._events.maxListeners = n;\r
+      if (!this._conf) this._conf = {};\r
+      this._conf.maxListeners = n;\r
+    }\r
+  };\r
+\r
+  EventEmitter.prototype.event = '';\r
+\r
+  EventEmitter.prototype.once = function(event, fn) {\r
+    this.many(event, 1, fn);\r
+    return this;\r
+  };\r
+\r
+  EventEmitter.prototype.many = function(event, ttl, fn) {\r
+    var self = this;\r
+\r
+    if (typeof fn !== 'function') {\r
+      throw new Error('many only accepts instances of Function');\r
+    }\r
+\r
+    function listener() {\r
+      if (--ttl === 0) {\r
+        self.off(event, listener);\r
+      }\r
+      fn.apply(this, arguments);\r
+    }\r
+\r
+    listener._origin = fn;\r
+\r
+    this.on(event, listener);\r
+\r
+    return self;\r
+  };\r
+\r
+  EventEmitter.prototype.emit = function() {\r
+\r
+    this._events || init.call(this);\r
+\r
+    var type = arguments[0];\r
+\r
+    if (type === 'newListener' && !this.newListener) {\r
+      if (!this._events.newListener) {\r
+        return false;\r
+      }\r
+    }\r
+\r
+    var al = arguments.length;\r
+    var args,l,i,j;\r
+    var handler;\r
+\r
+    if (this._all && this._all.length) {\r
+      handler = this._all.slice();\r
+      if (al > 3) {\r
+        args = new Array(al);\r
+        for (j = 0; j < al; j++) args[j] = arguments[j];\r
+      }\r
+\r
+      for (i = 0, l = handler.length; i < l; i++) {\r
+        this.event = type;\r
+        switch (al) {\r
+        case 1:\r
+          handler[i].call(this, type);\r
+          break;\r
+        case 2:\r
+          handler[i].call(this, type, arguments[1]);\r
+          break;\r
+        case 3:\r
+          handler[i].call(this, type, arguments[1], arguments[2]);\r
+          break;\r
+        default:\r
+          handler[i].apply(this, args);\r
+        }\r
+      }\r
+    }\r
+\r
+    if (this.wildcard) {\r
+      handler = [];\r
+      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();\r
+      searchListenerTree.call(this, handler, ns, this.listenerTree, 0);\r
+    } else {\r
+      handler = this._events[type];\r
+      if (typeof handler === 'function') {\r
+        this.event = type;\r
+        switch (al) {\r
+        case 1:\r
+          handler.call(this);\r
+          break;\r
+        case 2:\r
+          handler.call(this, arguments[1]);\r
+          break;\r
+        case 3:\r
+          handler.call(this, arguments[1], arguments[2]);\r
+          break;\r
+        default:\r
+          args = new Array(al - 1);\r
+          for (j = 1; j < al; j++) args[j - 1] = arguments[j];\r
+          handler.apply(this, args);\r
+        }\r
+        return true;\r
+      } else if (handler) {\r
+        // need to make copy of handlers because list can change in the middle\r
+        // of emit call\r
+        handler = handler.slice();\r
+      }\r
+    }\r
+\r
+    if (handler && handler.length) {\r
+      if (al > 3) {\r
+        args = new Array(al - 1);\r
+        for (j = 1; j < al; j++) args[j - 1] = arguments[j];\r
+      }\r
+      for (i = 0, l = handler.length; i < l; i++) {\r
+        this.event = type;\r
+        switch (al) {\r
+        case 1:\r
+          handler[i].call(this);\r
+          break;\r
+        case 2:\r
+          handler[i].call(this, arguments[1]);\r
+          break;\r
+        case 3:\r
+          handler[i].call(this, arguments[1], arguments[2]);\r
+          break;\r
+        default:\r
+          handler[i].apply(this, args);\r
+        }\r
+      }\r
+      return true;\r
+    } else if (!this._all && type === 'error') {\r
+      if (arguments[1] instanceof Error) {\r
+        throw arguments[1]; // Unhandled 'error' event\r
+      } else {\r
+        throw new Error("Uncaught, unspecified 'error' event.");\r
+      }\r
+      return false;\r
+    }\r
+\r
+    return !!this._all;\r
+  };\r
+\r
+  EventEmitter.prototype.emitAsync = function() {\r
+\r
+    this._events || init.call(this);\r
+\r
+    var type = arguments[0];\r
+\r
+    if (type === 'newListener' && !this.newListener) {\r
+        if (!this._events.newListener) { return Promise.resolve([false]); }\r
+    }\r
+\r
+    var promises= [];\r
+\r
+    var al = arguments.length;\r
+    var args,l,i,j;\r
+    var handler;\r
+\r
+    if (this._all) {\r
+      if (al > 3) {\r
+        args = new Array(al);\r
+        for (j = 1; j < al; j++) args[j] = arguments[j];\r
+      }\r
+      for (i = 0, l = this._all.length; i < l; i++) {\r
+        this.event = type;\r
+        switch (al) {\r
+        case 1:\r
+          promises.push(this._all[i].call(this, type));\r
+          break;\r
+        case 2:\r
+          promises.push(this._all[i].call(this, type, arguments[1]));\r
+          break;\r
+        case 3:\r
+          promises.push(this._all[i].call(this, type, arguments[1], arguments[2]));\r
+          break;\r
+        default:\r
+          promises.push(this._all[i].apply(this, args));\r
+        }\r
+      }\r
+    }\r
+\r
+    if (this.wildcard) {\r
+      handler = [];\r
+      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();\r
+      searchListenerTree.call(this, handler, ns, this.listenerTree, 0);\r
+    } else {\r
+      handler = this._events[type];\r
+    }\r
+\r
+    if (typeof handler === 'function') {\r
+      this.event = type;\r
+      switch (al) {\r
+      case 1:\r
+        promises.push(handler.call(this));\r
+        break;\r
+      case 2:\r
+        promises.push(handler.call(this, arguments[1]));\r
+        break;\r
+      case 3:\r
+        promises.push(handler.call(this, arguments[1], arguments[2]));\r
+        break;\r
+      default:\r
+        args = new Array(al - 1);\r
+        for (j = 1; j < al; j++) args[j - 1] = arguments[j];\r
+        promises.push(handler.apply(this, args));\r
+      }\r
+    } else if (handler && handler.length) {\r
+      if (al > 3) {\r
+        args = new Array(al - 1);\r
+        for (j = 1; j < al; j++) args[j - 1] = arguments[j];\r
+      }\r
+      for (i = 0, l = handler.length; i < l; i++) {\r
+        this.event = type;\r
+        switch (al) {\r
+        case 1:\r
+          promises.push(handler[i].call(this));\r
+          break;\r
+        case 2:\r
+          promises.push(handler[i].call(this, arguments[1]));\r
+          break;\r
+        case 3:\r
+          promises.push(handler[i].call(this, arguments[1], arguments[2]));\r
+          break;\r
+        default:\r
+          promises.push(handler[i].apply(this, args));\r
+        }\r
+      }\r
+    } else if (!this._all && type === 'error') {\r
+      if (arguments[1] instanceof Error) {\r
+        return Promise.reject(arguments[1]); // Unhandled 'error' event\r
+      } else {\r
+        return Promise.reject("Uncaught, unspecified 'error' event.");\r
+      }\r
+    }\r
+\r
+    return Promise.all(promises);\r
+  };\r
+\r
+  EventEmitter.prototype.on = function(type, listener) {\r
+    if (typeof type === 'function') {\r
+      this.onAny(type);\r
+      return this;\r
+    }\r
+\r
+    if (typeof listener !== 'function') {\r
+      throw new Error('on only accepts instances of Function');\r
+    }\r
+    this._events || init.call(this);\r
+\r
+    // To avoid recursion in the case that type == "newListeners"! Before\r
+    // adding it to the listeners, first emit "newListeners".\r
+    this.emit('newListener', type, listener);\r
+\r
+    if (this.wildcard) {\r
+      growListenerTree.call(this, type, listener);\r
+      return this;\r
+    }\r
+\r
+    if (!this._events[type]) {\r
+      // Optimize the case of one listener. Don't need the extra array object.\r
+      this._events[type] = listener;\r
+    }\r
+    else {\r
+      if (typeof this._events[type] === 'function') {\r
+        // Change to array.\r
+        this._events[type] = [this._events[type]];\r
+      }\r
+\r
+      // If we've already got an array, just append.\r
+      this._events[type].push(listener);\r
+\r
+      // Check for listener leak\r
+      if (\r
+        !this._events[type].warned &&\r
+        this._events.maxListeners > 0 &&\r
+        this._events[type].length > this._events.maxListeners\r
+      ) {\r
+        this._events[type].warned = true;\r
+        logPossibleMemoryLeak.call(this, this._events[type].length, type);\r
+      }\r
+    }\r
+\r
+    return this;\r
+  };\r
+\r
+  EventEmitter.prototype.onAny = function(fn) {\r
+    if (typeof fn !== 'function') {\r
+      throw new Error('onAny only accepts instances of Function');\r
+    }\r
+\r
+    if (!this._all) {\r
+      this._all = [];\r
+    }\r
+\r
+    // Add the function to the event listener collection.\r
+    this._all.push(fn);\r
+    return this;\r
+  };\r
+\r
+  EventEmitter.prototype.addListener = EventEmitter.prototype.on;\r
+\r
+  EventEmitter.prototype.off = function(type, listener) {\r
+    if (typeof listener !== 'function') {\r
+      throw new Error('removeListener only takes instances of Function');\r
+    }\r
+\r
+    var handlers,leafs=[];\r
+\r
+    if(this.wildcard) {\r
+      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();\r
+      leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);\r
+    }\r
+    else {\r
+      // does not use listeners(), so no side effect of creating _events[type]\r
+      if (!this._events[type]) return this;\r
+      handlers = this._events[type];\r
+      leafs.push({_listeners:handlers});\r
+    }\r
+\r
+    for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {\r
+      var leaf = leafs[iLeaf];\r
+      handlers = leaf._listeners;\r
+      if (isArray(handlers)) {\r
+\r
+        var position = -1;\r
+\r
+        for (var i = 0, length = handlers.length; i < length; i++) {\r
+          if (handlers[i] === listener ||\r
+            (handlers[i].listener && handlers[i].listener === listener) ||\r
+            (handlers[i]._origin && handlers[i]._origin === listener)) {\r
+            position = i;\r
+            break;\r
+          }\r
+        }\r
+\r
+        if (position < 0) {\r
+          continue;\r
+        }\r
+\r
+        if(this.wildcard) {\r
+          leaf._listeners.splice(position, 1);\r
+        }\r
+        else {\r
+          this._events[type].splice(position, 1);\r
+        }\r
+\r
+        if (handlers.length === 0) {\r
+          if(this.wildcard) {\r
+            delete leaf._listeners;\r
+          }\r
+          else {\r
+            delete this._events[type];\r
+          }\r
+        }\r
+\r
+        this.emit("removeListener", type, listener);\r
+\r
+        return this;\r
+      }\r
+      else if (handlers === listener ||\r
+        (handlers.listener && handlers.listener === listener) ||\r
+        (handlers._origin && handlers._origin === listener)) {\r
+        if(this.wildcard) {\r
+          delete leaf._listeners;\r
+        }\r
+        else {\r
+          delete this._events[type];\r
+        }\r
+\r
+        this.emit("removeListener", type, listener);\r
+      }\r
+    }\r
+\r
+    function recursivelyGarbageCollect(root) {\r
+      if (root === undefined) {\r
+        return;\r
+      }\r
+      var keys = Object.keys(root);\r
+      for (var i in keys) {\r
+        var key = keys[i];\r
+        var obj = root[key];\r
+        if ((obj instanceof Function) || (typeof obj !== "object") || (obj === null))\r
+          continue;\r
+        if (Object.keys(obj).length > 0) {\r
+          recursivelyGarbageCollect(root[key]);\r
+        }\r
+        if (Object.keys(obj).length === 0) {\r
+          delete root[key];\r
+        }\r
+      }\r
+    }\r
+    recursivelyGarbageCollect(this.listenerTree);\r
+\r
+    return this;\r
+  };\r
+\r
+  EventEmitter.prototype.offAny = function(fn) {\r
+    var i = 0, l = 0, fns;\r
+    if (fn && this._all && this._all.length > 0) {\r
+      fns = this._all;\r
+      for(i = 0, l = fns.length; i < l; i++) {\r
+        if(fn === fns[i]) {\r
+          fns.splice(i, 1);\r
+          this.emit("removeListenerAny", fn);\r
+          return this;\r
+        }\r
+      }\r
+    } else {\r
+      fns = this._all;\r
+      for(i = 0, l = fns.length; i < l; i++)\r
+        this.emit("removeListenerAny", fns[i]);\r
+      this._all = [];\r
+    }\r
+    return this;\r
+  };\r
+\r
+  EventEmitter.prototype.removeListener = EventEmitter.prototype.off;\r
+\r
+  EventEmitter.prototype.removeAllListeners = function(type) {\r
+    if (arguments.length === 0) {\r
+      !this._events || init.call(this);\r
+      return this;\r
+    }\r
+\r
+    if (this.wildcard) {\r
+      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();\r
+      var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0);\r
+\r
+      for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) {\r
+        var leaf = leafs[iLeaf];\r
+        leaf._listeners = null;\r
+      }\r
+    }\r
+    else if (this._events) {\r
+      this._events[type] = null;\r
+    }\r
+    return this;\r
+  };\r
+\r
+  EventEmitter.prototype.listeners = function(type) {\r
+    if (this.wildcard) {\r
+      var handlers = [];\r
+      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();\r
+      searchListenerTree.call(this, handlers, ns, this.listenerTree, 0);\r
+      return handlers;\r
+    }\r
+\r
+    this._events || init.call(this);\r
+\r
+    if (!this._events[type]) this._events[type] = [];\r
+    if (!isArray(this._events[type])) {\r
+      this._events[type] = [this._events[type]];\r
+    }\r
+    return this._events[type];\r
+  };\r
+\r
+  EventEmitter.prototype.listenerCount = function(type) {\r
+    return this.listeners(type).length;\r
+  };\r
+\r
+  EventEmitter.prototype.listenersAny = function() {\r
+\r
+    if(this._all) {\r
+      return this._all;\r
+    }\r
+    else {\r
+      return [];\r
+    }\r
+\r
+  };\r
+\r
+  if (typeof define === 'function' && define.amd) {\r
+     // AMD. Register as an anonymous module.\r
+    define(function() {\r
+      return EventEmitter;\r
+    });\r
+  } else if (typeof exports === 'object') {\r
+    // CommonJS\r
+    module.exports = EventEmitter;\r
+  }\r
+  else {\r
+    // Browser global.\r
+    window.EventEmitter2 = EventEmitter;\r
+  }\r
+}();\r
+
+},{}],2:[function(require,module,exports){
+/*
+object-assign
+(c) Sindre Sorhus
+@license MIT
+*/
+
+'use strict';
+/* eslint-disable no-unused-vars */
+var getOwnPropertySymbols = Object.getOwnPropertySymbols;
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+var propIsEnumerable = Object.prototype.propertyIsEnumerable;
+
+function toObject(val) {
+       if (val === null || val === undefined) {
+               throw new TypeError('Object.assign cannot be called with null or undefined');
+       }
+
+       return Object(val);
+}
+
+function shouldUseNative() {
+       try {
+               if (!Object.assign) {
+                       return false;
+               }
+
+               // Detect buggy property enumeration order in older V8 versions.
+
+               // https://bugs.chromium.org/p/v8/issues/detail?id=4118
+               var test1 = new String('abc');  // eslint-disable-line no-new-wrappers
+               test1[5] = 'de';
+               if (Object.getOwnPropertyNames(test1)[0] === '5') {
+                       return false;
+               }
+
+               // https://bugs.chromium.org/p/v8/issues/detail?id=3056
+               var test2 = {};
+               for (var i = 0; i < 10; i++) {
+                       test2['_' + String.fromCharCode(i)] = i;
+               }
+               var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
+                       return test2[n];
+               });
+               if (order2.join('') !== '0123456789') {
+                       return false;
+               }
+
+               // https://bugs.chromium.org/p/v8/issues/detail?id=3056
+               var test3 = {};
+               'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
+                       test3[letter] = letter;
+               });
+               if (Object.keys(Object.assign({}, test3)).join('') !==
+                               'abcdefghijklmnopqrst') {
+                       return false;
+               }
+
+               return true;
+       } catch (err) {
+               // We don't expect any of the above to throw, but better to be safe.
+               return false;
+       }
+}
+
+module.exports = shouldUseNative() ? Object.assign : function (target, source) {
+       var from;
+       var to = toObject(target);
+       var symbols;
+
+       for (var s = 1; s < arguments.length; s++) {
+               from = Object(arguments[s]);
+
+               for (var key in from) {
+                       if (hasOwnProperty.call(from, key)) {
+                               to[key] = from[key];
+                       }
+               }
+
+               if (getOwnPropertySymbols) {
+                       symbols = getOwnPropertySymbols(from);
+                       for (var i = 0; i < symbols.length; i++) {
+                               if (propIsEnumerable.call(from, symbols[i])) {
+                                       to[symbols[i]] = from[symbols[i]];
+                               }
+                       }
+               }
+       }
+
+       return to;
+};
+
+},{}],3:[function(require,module,exports){
+/**
+ * @fileOverview
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+/**
+ * If you use roslib in a browser, all the classes will be exported to a global variable called ROSLIB.
+ *
+ * If you use nodejs, this is the variable you get when you require('roslib')
+ */
+var ROSLIB = this.ROSLIB || {
+  REVISION : '0.20.0'
+};
+
+var assign = require('object-assign');
+
+// Add core components
+assign(ROSLIB, require('./core'));
+
+assign(ROSLIB, require('./actionlib'));
+
+assign(ROSLIB, require('./math'));
+
+assign(ROSLIB, require('./tf'));
+
+assign(ROSLIB, require('./urdf'));
+
+module.exports = ROSLIB;
+
+},{"./actionlib":9,"./core":18,"./math":23,"./tf":26,"./urdf":38,"object-assign":2}],4:[function(require,module,exports){
+(function (global){
+global.ROSLIB = require('./RosLib');
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./RosLib":3}],5:[function(require,module,exports){
+/**
+ * @fileOverview
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var Topic = require('../core/Topic');
+var Message = require('../core/Message');
+var EventEmitter2 = require('eventemitter2').EventEmitter2;
+
+/**
+ * An actionlib action client.
+ *
+ * Emits the following events:
+ *  * 'timeout' - if a timeout occurred while sending a goal
+ *  * 'status' - the status messages received from the action server
+ *  * 'feedback' -  the feedback messages received from the action server
+ *  * 'result' - the result returned from the action server
+ *
+ *  @constructor
+ *  @param options - object with following keys:
+ *   * ros - the ROSLIB.Ros connection handle
+ *   * serverName - the action server name, like /fibonacci
+ *   * actionName - the action message name, like 'actionlib_tutorials/FibonacciAction'
+ *   * timeout - the timeout length when connecting to the action server
+ */
+function ActionClient(options) {
+  var that = this;
+  options = options || {};
+  this.ros = options.ros;
+  this.serverName = options.serverName;
+  this.actionName = options.actionName;
+  this.timeout = options.timeout;
+  this.omitFeedback = options.omitFeedback;
+  this.omitStatus = options.omitStatus;
+  this.omitResult = options.omitResult;
+  this.goals = {};
+
+  // flag to check if a status has been received
+  var receivedStatus = false;
+
+  // create the topics associated with actionlib
+  this.feedbackListener = new Topic({
+    ros : this.ros,
+    name : this.serverName + '/feedback',
+    messageType : this.actionName + 'Feedback'
+  });
+
+  this.statusListener = new Topic({
+    ros : this.ros,
+    name : this.serverName + '/status',
+    messageType : 'actionlib_msgs/GoalStatusArray'
+  });
+
+  this.resultListener = new Topic({
+    ros : this.ros,
+    name : this.serverName + '/result',
+    messageType : this.actionName + 'Result'
+  });
+
+  this.goalTopic = new Topic({
+    ros : this.ros,
+    name : this.serverName + '/goal',
+    messageType : this.actionName + 'Goal'
+  });
+
+  this.cancelTopic = new Topic({
+    ros : this.ros,
+    name : this.serverName + '/cancel',
+    messageType : 'actionlib_msgs/GoalID'
+  });
+
+  // advertise the goal and cancel topics
+  this.goalTopic.advertise();
+  this.cancelTopic.advertise();
+
+  // subscribe to the status topic
+  if (!this.omitStatus) {
+    this.statusListener.subscribe(function(statusMessage) {
+      receivedStatus = true;
+      statusMessage.status_list.forEach(function(status) {
+        var goal = that.goals[status.goal_id.id];
+        if (goal) {
+          goal.emit('status', status);
+        }
+      });
+    });
+  }
+
+  // subscribe the the feedback topic
+  if (!this.omitFeedback) {
+    this.feedbackListener.subscribe(function(feedbackMessage) {
+      var goal = that.goals[feedbackMessage.status.goal_id.id];
+      if (goal) {
+        goal.emit('status', feedbackMessage.status);
+        goal.emit('feedback', feedbackMessage.feedback);
+      }
+    });
+  }
+
+  // subscribe to the result topic
+  if (!this.omitResult) {
+    this.resultListener.subscribe(function(resultMessage) {
+      var goal = that.goals[resultMessage.status.goal_id.id];
+
+      if (goal) {
+        goal.emit('status', resultMessage.status);
+        goal.emit('result', resultMessage.result);
+      }
+    });
+  }
+
+  // If timeout specified, emit a 'timeout' event if the action server does not respond
+  if (this.timeout) {
+    setTimeout(function() {
+      if (!receivedStatus) {
+        that.emit('timeout');
+      }
+    }, this.timeout);
+  }
+}
+
+ActionClient.prototype.__proto__ = EventEmitter2.prototype;
+
+/**
+ * Cancel all goals associated with this ActionClient.
+ */
+ActionClient.prototype.cancel = function() {
+  var cancelMessage = new Message();
+  this.cancelTopic.publish(cancelMessage);
+};
+
+/**
+ * Unsubscribe and unadvertise all topics associated with this ActionClient.
+ */
+ActionClient.prototype.dispose = function() {
+  this.goalTopic.unadvertise();
+  this.cancelTopic.unadvertise();
+  if (!this.omitStatus) {this.statusListener.unsubscribe();}
+  if (!this.omitFeedback) {this.feedbackListener.unsubscribe();}
+  if (!this.omitResult) {this.resultListener.unsubscribe();}
+};
+
+module.exports = ActionClient;
+
+},{"../core/Message":10,"../core/Topic":17,"eventemitter2":1}],6:[function(require,module,exports){
+/**
+ * @fileOverview
+ * @author Justin Young - justin@oodar.com.au
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var Topic = require('../core/Topic');
+var Message = require('../core/Message');
+var EventEmitter2 = require('eventemitter2').EventEmitter2;
+
+/**
+ * An actionlib action listener
+ *
+ * Emits the following events:
+ *  * 'status' - the status messages received from the action server
+ *  * 'feedback' -  the feedback messages received from the action server
+ *  * 'result' - the result returned from the action server
+ *
+ *  @constructor
+ *  @param options - object with following keys:
+ *   * ros - the ROSLIB.Ros connection handle
+ *   * serverName - the action server name, like /fibonacci
+ *   * actionName - the action message name, like 'actionlib_tutorials/FibonacciAction'
+ */
+function ActionListener(options) {
+  var that = this;
+  options = options || {};
+  this.ros = options.ros;
+  this.serverName = options.serverName;
+  this.actionName = options.actionName;
+  this.timeout = options.timeout;
+  this.omitFeedback = options.omitFeedback;
+  this.omitStatus = options.omitStatus;
+  this.omitResult = options.omitResult;
+
+
+  // create the topics associated with actionlib
+  var goalListener = new Topic({
+    ros : this.ros,
+    name : this.serverName + '/goal',
+    messageType : this.actionName + 'Goal'
+  });
+
+  var feedbackListener = new Topic({
+    ros : this.ros,
+    name : this.serverName + '/feedback',
+    messageType : this.actionName + 'Feedback'
+  });
+
+  var statusListener = new Topic({
+    ros : this.ros,
+    name : this.serverName + '/status',
+    messageType : 'actionlib_msgs/GoalStatusArray'
+  });
+
+  var resultListener = new Topic({
+    ros : this.ros,
+    name : this.serverName + '/result',
+    messageType : this.actionName + 'Result'
+  });
+
+  goalListener.subscribe(function(goalMessage) {
+      that.emit('goal', goalMessage);
+  });
+
+  statusListener.subscribe(function(statusMessage) {
+      statusMessage.status_list.forEach(function(status) {
+          that.emit('status', status);
+      });
+  });
+
+  feedbackListener.subscribe(function(feedbackMessage) {
+      that.emit('status', feedbackMessage.status);
+      that.emit('feedback', feedbackMessage.feedback);
+  });
+
+  // subscribe to the result topic
+  resultListener.subscribe(function(resultMessage) {
+      that.emit('status', resultMessage.status);
+      that.emit('result', resultMessage.result);
+  });
+
+}
+
+ActionListener.prototype.__proto__ = EventEmitter2.prototype;
+
+module.exports = ActionListener;
+
+},{"../core/Message":10,"../core/Topic":17,"eventemitter2":1}],7:[function(require,module,exports){
+/**
+ * @fileOverview
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var Message = require('../core/Message');
+var EventEmitter2 = require('eventemitter2').EventEmitter2;
+
+/**
+ * An actionlib goal goal is associated with an action server.
+ *
+ * Emits the following events:
+ *  * 'timeout' - if a timeout occurred while sending a goal
+ *
+ *  @constructor
+ *  @param object with following keys:
+ *   * actionClient - the ROSLIB.ActionClient to use with this goal
+ *   * goalMessage - The JSON object containing the goal for the action server
+ */
+function Goal(options) {
+  var that = this;
+  this.actionClient = options.actionClient;
+  this.goalMessage = options.goalMessage;
+  this.isFinished = false;
+
+  // Used to create random IDs
+  var date = new Date();
+
+  // Create a random ID
+  this.goalID = 'goal_' + Math.random() + '_' + date.getTime();
+  // Fill in the goal message
+  this.goalMessage = new Message({
+    goal_id : {
+      stamp : {
+        secs : 0,
+        nsecs : 0
+      },
+      id : this.goalID
+    },
+    goal : this.goalMessage
+  });
+
+  this.on('status', function(status) {
+    that.status = status;
+  });
+
+  this.on('result', function(result) {
+    that.isFinished = true;
+    that.result = result;
+  });
+
+  this.on('feedback', function(feedback) {
+    that.feedback = feedback;
+  });
+
+  // Add the goal
+  this.actionClient.goals[this.goalID] = this;
+}
+
+Goal.prototype.__proto__ = EventEmitter2.prototype;
+
+/**
+ * Send the goal to the action server.
+ *
+ * @param timeout (optional) - a timeout length for the goal's result
+ */
+Goal.prototype.send = function(timeout) {
+  var that = this;
+  that.actionClient.goalTopic.publish(that.goalMessage);
+  if (timeout) {
+    setTimeout(function() {
+      if (!that.isFinished) {
+        that.emit('timeout');
+      }
+    }, timeout);
+  }
+};
+
+/**
+ * Cancel the current goal.
+ */
+Goal.prototype.cancel = function() {
+  var cancelMessage = new Message({
+    id : this.goalID
+  });
+  this.actionClient.cancelTopic.publish(cancelMessage);
+};
+
+module.exports = Goal;
+},{"../core/Message":10,"eventemitter2":1}],8:[function(require,module,exports){
+/**
+ * @fileOverview
+ * @author Laura Lindzey - lindzey@gmail.com
+ */
+
+var Topic = require('../core/Topic');
+var Message = require('../core/Message');
+var EventEmitter2 = require('eventemitter2').EventEmitter2;
+
+/**
+ * An actionlib action server client.
+ *
+ * Emits the following events:
+ *  * 'goal' - goal sent by action client
+ *  * 'cancel' - action client has canceled the request
+ *
+ *  @constructor
+ *  @param options - object with following keys:
+ *   * ros - the ROSLIB.Ros connection handle
+ *   * serverName - the action server name, like /fibonacci
+ *   * actionName - the action message name, like 'actionlib_tutorials/FibonacciAction'
+ */
+
+function SimpleActionServer(options) {
+    var that = this;
+    options = options || {};
+    this.ros = options.ros;
+    this.serverName = options.serverName;
+    this.actionName = options.actionName;
+
+    // create and advertise publishers
+    this.feedbackPublisher = new Topic({
+        ros : this.ros,
+        name : this.serverName + '/feedback',
+        messageType : this.actionName + 'Feedback'
+    });
+    this.feedbackPublisher.advertise();
+
+    var statusPublisher = new Topic({
+        ros : this.ros,
+        name : this.serverName + '/status',
+        messageType : 'actionlib_msgs/GoalStatusArray'
+    });
+    statusPublisher.advertise();
+
+    this.resultPublisher = new Topic({
+        ros : this.ros,
+        name : this.serverName + '/result',
+        messageType : this.actionName + 'Result'
+    });
+    this.resultPublisher.advertise();
+
+    // create and subscribe to listeners
+    var goalListener = new Topic({
+        ros : this.ros,
+        name : this.serverName + '/goal',
+        messageType : this.actionName + 'Goal'
+    });
+
+    var cancelListener = new Topic({
+        ros : this.ros,
+        name : this.serverName + '/cancel',
+        messageType : 'actionlib_msgs/GoalID'
+    });
+
+    // Track the goals and their status in order to publish status...
+    this.statusMessage = new Message({
+        header : {
+            stamp : {secs : 0, nsecs : 100},
+            frame_id : ''
+        },
+        status_list : []
+    });
+
+    // needed for handling preemption prompted by a new goal being received
+    this.currentGoal = null; // currently tracked goal
+    this.nextGoal = null; // the one that'll be preempting
+
+    goalListener.subscribe(function(goalMessage) {
+        
+    if(that.currentGoal) {
+            that.nextGoal = goalMessage;
+            // needs to happen AFTER rest is set up
+            that.emit('cancel');
+    } else {
+            that.statusMessage.status_list = [{goal_id : goalMessage.goal_id, status : 1}];
+            that.currentGoal = goalMessage;
+            that.emit('goal', goalMessage.goal);
+    }
+    });
+
+    // helper function for determing ordering of timestamps
+    // returns t1 < t2
+    var isEarlier = function(t1, t2) {
+        if(t1.secs > t2.secs) {
+            return false;
+        } else if(t1.secs < t2.secs) {
+            return true;
+        } else if(t1.nsecs < t2.nsecs) {
+            return true;
+        } else {
+            return false;
+        }
+    };
+
+    // TODO: this may be more complicated than necessary, since I'm
+    // not sure if the callbacks can ever wind up with a scenario
+    // where we've been preempted by a next goal, it hasn't finished
+    // processing, and then we get a cancel message
+    cancelListener.subscribe(function(cancelMessage) {
+
+        // cancel ALL goals if both empty
+        if(cancelMessage.stamp.secs === 0 && cancelMessage.stamp.secs === 0 && cancelMessage.id === '') {
+            that.nextGoal = null;
+            if(that.currentGoal) {
+                that.emit('cancel');
+            }
+        } else { // treat id and stamp independently
+            if(that.currentGoal && cancelMessage.id === that.currentGoal.goal_id.id) {
+                that.emit('cancel');
+            } else if(that.nextGoal && cancelMessage.id === that.nextGoal.goal_id.id) {
+                that.nextGoal = null;
+            }
+
+            if(that.nextGoal && isEarlier(that.nextGoal.goal_id.stamp,
+                                          cancelMessage.stamp)) {
+                that.nextGoal = null;
+            }
+            if(that.currentGoal && isEarlier(that.currentGoal.goal_id.stamp,
+                                             cancelMessage.stamp)) {
+                
+                that.emit('cancel');
+            }
+        }
+    });
+
+    // publish status at pseudo-fixed rate; required for clients to know they've connected
+    var statusInterval = setInterval( function() {
+        var currentTime = new Date();
+        var secs = Math.floor(currentTime.getTime()/1000);
+        var nsecs = Math.round(1000000000*(currentTime.getTime()/1000-secs));
+        that.statusMessage.header.stamp.secs = secs;
+        that.statusMessage.header.stamp.nsecs = nsecs;
+        statusPublisher.publish(that.statusMessage);
+    }, 500); // publish every 500ms
+
+}
+
+SimpleActionServer.prototype.__proto__ = EventEmitter2.prototype;
+
+/**
+*  Set action state to succeeded and return to client
+*/
+
+SimpleActionServer.prototype.setSucceeded = function(result2) {
+    
+
+    var resultMessage = new Message({
+        status : {goal_id : this.currentGoal.goal_id, status : 3},
+        result : result2
+    });
+    this.resultPublisher.publish(resultMessage);
+
+    this.statusMessage.status_list = [];
+    if(this.nextGoal) {
+        this.currentGoal = this.nextGoal;
+        this.nextGoal = null;
+        this.emit('goal', this.currentGoal.goal);
+    } else {
+        this.currentGoal = null;
+    }
+};
+
+/**
+*  Function to send feedback
+*/
+
+SimpleActionServer.prototype.sendFeedback = function(feedback2) {
+
+    var feedbackMessage = new Message({
+        status : {goal_id : this.currentGoal.goal_id, status : 1},
+        feedback : feedback2
+    });
+    this.feedbackPublisher.publish(feedbackMessage);
+};
+
+/**
+*  Handle case where client requests preemption
+*/
+
+SimpleActionServer.prototype.setPreempted = function() {
+
+    this.statusMessage.status_list = [];
+    var resultMessage = new Message({
+        status : {goal_id : this.currentGoal.goal_id, status : 2},
+    });
+    this.resultPublisher.publish(resultMessage);
+
+    if(this.nextGoal) {
+        this.currentGoal = this.nextGoal;
+        this.nextGoal = null;
+        this.emit('goal', this.currentGoal.goal);
+    } else {
+        this.currentGoal = null;
+    }
+};
+
+module.exports = SimpleActionServer;
+},{"../core/Message":10,"../core/Topic":17,"eventemitter2":1}],9:[function(require,module,exports){
+var Ros = require('../core/Ros');
+var mixin = require('../mixin');
+
+var action = module.exports = {
+    ActionClient: require('./ActionClient'),
+    ActionListener: require('./ActionListener'),
+    Goal: require('./Goal'),
+    SimpleActionServer: require('./SimpleActionServer')
+};
+
+mixin(Ros, ['ActionClient', 'SimpleActionServer'], action);
+
+},{"../core/Ros":12,"../mixin":24,"./ActionClient":5,"./ActionListener":6,"./Goal":7,"./SimpleActionServer":8}],10:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author Brandon Alexander - baalexander@gmail.com
+ */
+
+var assign = require('object-assign');
+
+/**
+ * Message objects are used for publishing and subscribing to and from topics.
+ *
+ * @constructor
+ * @param values - object matching the fields defined in the .msg definition file
+ */
+function Message(values) {
+  assign(this, values);
+}
+
+module.exports = Message;
+},{"object-assign":2}],11:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author Brandon Alexander - baalexander@gmail.com
+ */
+
+var Service = require('./Service');
+var ServiceRequest = require('./ServiceRequest');
+
+/**
+ * A ROS parameter.
+ *
+ * @constructor
+ * @param options - possible keys include:
+ *   * ros - the ROSLIB.Ros connection handle
+ *   * name - the param name, like max_vel_x
+ */
+function Param(options) {
+  options = options || {};
+  this.ros = options.ros;
+  this.name = options.name;
+}
+
+/**
+ * Fetches the value of the param.
+ *
+ * @param callback - function with the following params:
+ *  * value - the value of the param from ROS.
+ */
+Param.prototype.get = function(callback) {
+  var paramClient = new Service({
+    ros : this.ros,
+    name : '/rosapi/get_param',
+    serviceType : 'rosapi/GetParam'
+  });
+
+  var request = new ServiceRequest({
+    name : this.name
+  });
+
+  paramClient.callService(request, function(result) {
+    var value = JSON.parse(result.value);
+    callback(value);
+  });
+};
+
+/**
+ * Sets the value of the param in ROS.
+ *
+ * @param value - value to set param to.
+ */
+Param.prototype.set = function(value, callback) {
+  var paramClient = new Service({
+    ros : this.ros,
+    name : '/rosapi/set_param',
+    serviceType : 'rosapi/SetParam'
+  });
+
+  var request = new ServiceRequest({
+    name : this.name,
+    value : JSON.stringify(value)
+  });
+
+  paramClient.callService(request, callback);
+};
+
+/**
+ * Delete this parameter on the ROS server.
+ */
+Param.prototype.delete = function(callback) {
+  var paramClient = new Service({
+    ros : this.ros,
+    name : '/rosapi/delete_param',
+    serviceType : 'rosapi/DeleteParam'
+  });
+
+  var request = new ServiceRequest({
+    name : this.name
+  });
+
+  paramClient.callService(request, callback);
+};
+
+module.exports = Param;
+},{"./Service":13,"./ServiceRequest":14}],12:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author Brandon Alexander - baalexander@gmail.com
+ */
+
+var WebSocket = require('ws');
+var socketAdapter = require('./SocketAdapter.js');
+
+var Service = require('./Service');
+var ServiceRequest = require('./ServiceRequest');
+
+var assign = require('object-assign');
+var EventEmitter2 = require('eventemitter2').EventEmitter2;
+
+/**
+ * Manages connection to the server and all interactions with ROS.
+ *
+ * Emits the following events:
+ *  * 'error' - there was an error with ROS
+ *  * 'connection' - connected to the WebSocket server
+ *  * 'close' - disconnected to the WebSocket server
+ *  * <topicName> - a message came from rosbridge with the given topic name
+ *  * <serviceID> - a service response came from rosbridge with the given ID
+ *
+ * @constructor
+ * @param options - possible keys include: <br>
+ *   * url (optional) - (can be specified later with `connect`) the WebSocket URL for rosbridge or the node server url to connect using socket.io (if socket.io exists in the page) <br>
+ *   * groovyCompatibility - don't use interfaces that changed after the last groovy release or rosbridge_suite and related tools (defaults to true)
+ *   * transportLibrary (optional) - one of 'websocket' (default), 'socket.io' or RTCPeerConnection instance controlling how the connection is created in `connect`.
+ *   * transportOptions (optional) - the options to use use when creating a connection. Currently only used if `transportLibrary` is RTCPeerConnection.
+ */
+function Ros(options) {
+  options = options || {};
+  this.socket = null;
+  this.idCounter = 0;
+  this.isConnected = false;
+  this.transportLibrary = options.transportLibrary || 'websocket';
+  this.transportOptions = options.transportOptions || {};
+
+  if (typeof options.groovyCompatibility === 'undefined') {
+    this.groovyCompatibility = true;
+  }
+  else {
+    this.groovyCompatibility = options.groovyCompatibility;
+  }
+
+  // Sets unlimited event listeners.
+  this.setMaxListeners(0);
+
+  // begin by checking if a URL was given
+  if (options.url) {
+    this.connect(options.url);
+  }
+}
+
+Ros.prototype.__proto__ = EventEmitter2.prototype;
+
+/**
+ * Connect to the specified WebSocket.
+ *
+ * @param url - WebSocket URL or RTCDataChannel label for Rosbridge
+ */
+Ros.prototype.connect = function(url) {
+  if (this.transportLibrary === 'socket.io') {
+    this.socket = assign(io(url, {'force new connection': true}), socketAdapter(this));
+    this.socket.on('connect', this.socket.onopen);
+    this.socket.on('data', this.socket.onmessage);
+    this.socket.on('close', this.socket.onclose);
+    this.socket.on('error', this.socket.onerror);
+  } else if (this.transportLibrary.constructor.name === 'RTCPeerConnection') {
+    this.socket = assign(this.transportLibrary.createDataChannel(url, this.transportOptions), socketAdapter(this));
+  }else {
+    this.socket = assign(new WebSocket(url), socketAdapter(this));
+  }
+
+};
+
+/**
+ * Disconnect from the WebSocket server.
+ */
+Ros.prototype.close = function() {
+  if (this.socket) {
+    this.socket.close();
+  }
+};
+
+/**
+ * Sends an authorization request to the server.
+ *
+ * @param mac - MAC (hash) string given by the trusted source.
+ * @param client - IP of the client.
+ * @param dest - IP of the destination.
+ * @param rand - Random string given by the trusted source.
+ * @param t - Time of the authorization request.
+ * @param level - User level as a string given by the client.
+ * @param end - End time of the client's session.
+ */
+Ros.prototype.authenticate = function(mac, client, dest, rand, t, level, end) {
+  // create the request
+  var auth = {
+    op : 'auth',
+    mac : mac,
+    client : client,
+    dest : dest,
+    rand : rand,
+    t : t,
+    level : level,
+    end : end
+  };
+  // send the request
+  this.callOnConnection(auth);
+};
+
+/**
+ * Sends the message over the WebSocket, but queues the message up if not yet
+ * connected.
+ */
+Ros.prototype.callOnConnection = function(message) {
+  var that = this;
+  var messageJson = JSON.stringify(message);
+  var emitter = null;
+  if (this.transportLibrary === 'socket.io') {
+    emitter = function(msg){that.socket.emit('operation', msg);};
+  } else {
+    emitter = function(msg){that.socket.send(msg);};
+  }
+
+  if (!this.isConnected) {
+    that.once('connection', function() {
+      emitter(messageJson);
+    });
+  } else {
+    emitter(messageJson);
+  }
+};
+
+/**
+ * Sends a set_level request to the server
+ *
+ * @param level - Status level (none, error, warning, info)
+ * @param id - Optional: Operation ID to change status level on
+ */
+Ros.prototype.setStatusLevel = function(level, id){
+  var levelMsg = {
+    op: 'set_level',
+    level: level,
+    id: id
+  };
+
+  this.callOnConnection(levelMsg);
+};
+
+/**
+ * Retrieves Action Servers in ROS as an array of string
+ *
+ *   * actionservers - Array of action server names
+ */
+Ros.prototype.getActionServers = function(callback, failedCallback) {
+  var getActionServers = new Service({
+    ros : this,
+    name : '/rosapi/action_servers',
+    serviceType : 'rosapi/GetActionServers'
+  });
+
+  var request = new ServiceRequest({});
+  if (typeof failedCallback === 'function'){
+    getActionServers.callService(request,
+      function(result) {
+        callback(result.action_servers);
+      },
+      function(message){
+        failedCallback(message);
+      }
+    );
+  }else{
+    getActionServers.callService(request, function(result) {
+      callback(result.action_servers);
+    });
+  }
+};
+
+/**
+ * Retrieves list of topics in ROS as an array.
+ *
+ * @param callback function with params:
+ *   * topics - Array of topic names
+ */
+Ros.prototype.getTopics = function(callback, failedCallback) {
+  var topicsClient = new Service({
+    ros : this,
+    name : '/rosapi/topics',
+    serviceType : 'rosapi/Topics'
+  });
+
+  var request = new ServiceRequest();
+  if (typeof failedCallback === 'function'){
+    topicsClient.callService(request,
+      function(result) {
+        callback(result);
+      },
+      function(message){
+        failedCallback(message);
+      }
+    );
+  }else{
+    topicsClient.callService(request, function(result) {
+      callback(result);
+    });
+  }
+};
+
+/**
+ * Retrieves Topics in ROS as an array as specific type
+ *
+ * @param topicType topic type to find:
+ * @param callback function with params:
+ *   * topics - Array of topic names
+ */
+Ros.prototype.getTopicsForType = function(topicType, callback, failedCallback) {
+  var topicsForTypeClient = new Service({
+    ros : this,
+    name : '/rosapi/topics_for_type',
+    serviceType : 'rosapi/TopicsForType'
+  });
+
+  var request = new ServiceRequest({
+    type: topicType
+  });
+  if (typeof failedCallback === 'function'){
+    topicsForTypeClient.callService(request,
+      function(result) {
+        callback(result.topics);
+      },
+      function(message){
+        failedCallback(message);
+      }
+    );
+  }else{
+    topicsForTypeClient.callService(request, function(result) {
+      callback(result.topics);
+    });
+  }
+};
+
+/**
+ * Retrieves list of active service names in ROS.
+ *
+ * @param callback - function with the following params:
+ *   * services - array of service names
+ */
+Ros.prototype.getServices = function(callback, failedCallback) {
+  var servicesClient = new Service({
+    ros : this,
+    name : '/rosapi/services',
+    serviceType : 'rosapi/Services'
+  });
+
+  var request = new ServiceRequest();
+  if (typeof failedCallback === 'function'){
+    servicesClient.callService(request,
+      function(result) {
+        callback(result.services);
+      },
+      function(message) {
+        failedCallback(message);
+      }
+    );
+  }else{
+    servicesClient.callService(request, function(result) {
+      callback(result.services);
+    });
+  }
+};
+
+/**
+ * Retrieves list of services in ROS as an array as specific type
+ *
+ * @param serviceType service type to find:
+ * @param callback function with params:
+ *   * topics - Array of service names
+ */
+Ros.prototype.getServicesForType = function(serviceType, callback, failedCallback) {
+  var servicesForTypeClient = new Service({
+    ros : this,
+    name : '/rosapi/services_for_type',
+    serviceType : 'rosapi/ServicesForType'
+  });
+
+  var request = new ServiceRequest({
+    type: serviceType
+  });
+  if (typeof failedCallback === 'function'){
+    servicesForTypeClient.callService(request,
+      function(result) {
+        callback(result.services);
+      },
+      function(message) {
+        failedCallback(message);
+      }
+    );
+  }else{
+    servicesForTypeClient.callService(request, function(result) {
+      callback(result.services);
+    });
+  }
+};
+
+/**
+ * Retrieves a detail of ROS service request.
+ *
+ * @param service name of service:
+ * @param callback - function with params:
+ *   * type - String of the service type
+ */
+Ros.prototype.getServiceRequestDetails = function(type, callback, failedCallback) {
+  var serviceTypeClient = new Service({
+    ros : this,
+    name : '/rosapi/service_request_details',
+    serviceType : 'rosapi/ServiceRequestDetails'
+  });
+  var request = new ServiceRequest({
+    type: type
+  });
+
+  if (typeof failedCallback === 'function'){
+    serviceTypeClient.callService(request,
+      function(result) {
+        callback(result);
+      },
+      function(message){
+        failedCallback(message);
+      }
+    );
+  }else{
+    serviceTypeClient.callService(request, function(result) {
+      callback(result);
+    });
+  }
+};
+
+/**
+ * Retrieves a detail of ROS service request.
+ *
+ * @param service name of service:
+ * @param callback - function with params:
+ *   * type - String of the service type
+ */
+Ros.prototype.getServiceResponseDetails = function(type, callback, failedCallback) {
+  var serviceTypeClient = new Service({
+    ros : this,
+    name : '/rosapi/service_response_details',
+    serviceType : 'rosapi/ServiceResponseDetails'
+  });
+  var request = new ServiceRequest({
+    type: type
+  });
+
+  if (typeof failedCallback === 'function'){
+    serviceTypeClient.callService(request,
+      function(result) {
+        callback(result);
+      },
+      function(message){
+        failedCallback(message);
+      }
+    );
+  }else{
+    serviceTypeClient.callService(request, function(result) {
+      callback(result);
+    });
+  }
+};
+
+/**
+ * Retrieves list of active node names in ROS.
+ *
+ * @param callback - function with the following params:
+ *   * nodes - array of node names
+ */
+Ros.prototype.getNodes = function(callback, failedCallback) {
+  var nodesClient = new Service({
+    ros : this,
+    name : '/rosapi/nodes',
+    serviceType : 'rosapi/Nodes'
+  });
+
+  var request = new ServiceRequest();
+  if (typeof failedCallback === 'function'){
+    nodesClient.callService(request,
+      function(result) {
+        callback(result.nodes);
+      },
+      function(message) {
+        failedCallback(message);
+      }
+    );
+  }else{
+    nodesClient.callService(request, function(result) {
+      callback(result.nodes);
+    });
+  }
+};
+
+/**
+  * Retrieves list subscribed topics, publishing topics and services of a specific node
+  *
+  * @param node name of the node:
+  * @param callback - function with params:
+  *   * publications - array of published topic names
+  *   * subscriptions - array of subscribed topic names
+  *   * services - array of service names hosted
+  */
+Ros.prototype.getNodeDetails = function(node, callback, failedCallback) {
+  var nodesClient = new Service({
+    ros : this,
+    name : '/rosapi/node_details',
+    serviceType : 'rosapi/NodeDetails'
+  });
+
+  var request = new ServiceRequest({
+    node: node
+  });
+  if (typeof failedCallback === 'function'){
+    nodesClient.callService(request,
+      function(result) {
+        callback(result.subscribing, result.publishing, result.services);
+      },
+      function(message) {
+        failedCallback(message);
+      }
+    );
+  } else {
+    nodesClient.callService(request, function(result) {
+      callback(result);
+    });
+  }
+};
+
+/**
+ * Retrieves list of param names from the ROS Parameter Server.
+ *
+ * @param callback function with params:
+ *  * params - array of param names.
+ */
+Ros.prototype.getParams = function(callback, failedCallback) {
+  var paramsClient = new Service({
+    ros : this,
+    name : '/rosapi/get_param_names',
+    serviceType : 'rosapi/GetParamNames'
+  });
+  var request = new ServiceRequest();
+  if (typeof failedCallback === 'function'){
+    paramsClient.callService(request,
+      function(result) {
+        callback(result.names);
+      },
+      function(message){
+        failedCallback(message);
+      }
+    );
+  }else{
+    paramsClient.callService(request, function(result) {
+      callback(result.names);
+    });
+  }
+};
+
+/**
+ * Retrieves a type of ROS topic.
+ *
+ * @param topic name of the topic:
+ * @param callback - function with params:
+ *   * type - String of the topic type
+ */
+Ros.prototype.getTopicType = function(topic, callback, failedCallback) {
+  var topicTypeClient = new Service({
+    ros : this,
+    name : '/rosapi/topic_type',
+    serviceType : 'rosapi/TopicType'
+  });
+  var request = new ServiceRequest({
+    topic: topic
+  });
+
+  if (typeof failedCallback === 'function'){
+    topicTypeClient.callService(request,
+      function(result) {
+        callback(result.type);
+      },
+      function(message){
+        failedCallback(message);
+      }
+    );
+  }else{
+    topicTypeClient.callService(request, function(result) {
+      callback(result.type);
+    });
+  }
+};
+
+/**
+ * Retrieves a type of ROS service.
+ *
+ * @param service name of service:
+ * @param callback - function with params:
+ *   * type - String of the service type
+ */
+Ros.prototype.getServiceType = function(service, callback, failedCallback) {
+  var serviceTypeClient = new Service({
+    ros : this,
+    name : '/rosapi/service_type',
+    serviceType : 'rosapi/ServiceType'
+  });
+  var request = new ServiceRequest({
+    service: service
+  });
+
+  if (typeof failedCallback === 'function'){
+    serviceTypeClient.callService(request,
+      function(result) {
+        callback(result.type);
+      },
+      function(message){
+        failedCallback(message);
+      }
+    );
+  }else{
+    serviceTypeClient.callService(request, function(result) {
+      callback(result.type);
+    });
+  }
+};
+
+/**
+ * Retrieves a detail of ROS message.
+ *
+ * @param callback - function with params:
+ *   * details - Array of the message detail
+ * @param message - String of a topic type
+ */
+Ros.prototype.getMessageDetails = function(message, callback, failedCallback) {
+  var messageDetailClient = new Service({
+    ros : this,
+    name : '/rosapi/message_details',
+    serviceType : 'rosapi/MessageDetails'
+  });
+  var request = new ServiceRequest({
+    type: message
+  });
+
+  if (typeof failedCallback === 'function'){
+    messageDetailClient.callService(request,
+      function(result) {
+        callback(result.typedefs);
+      },
+      function(message){
+        failedCallback(message);
+      }
+    );
+  }else{
+    messageDetailClient.callService(request, function(result) {
+      callback(result.typedefs);
+    });
+  }
+};
+
+/**
+ * Decode a typedefs into a dictionary like `rosmsg show foo/bar`
+ *
+ * @param defs - array of type_def dictionary
+ */
+Ros.prototype.decodeTypeDefs = function(defs) {
+  var that = this;
+
+  // calls itself recursively to resolve type definition using hints.
+  var decodeTypeDefsRec = function(theType, hints) {
+    var typeDefDict = {};
+    for (var i = 0; i < theType.fieldnames.length; i++) {
+      var arrayLen = theType.fieldarraylen[i];
+      var fieldName = theType.fieldnames[i];
+      var fieldType = theType.fieldtypes[i];
+      if (fieldType.indexOf('/') === -1) { // check the fieldType includes '/' or not
+        if (arrayLen === -1) {
+          typeDefDict[fieldName] = fieldType;
+        }
+        else {
+          typeDefDict[fieldName] = [fieldType];
+        }
+      }
+      else {
+        // lookup the name
+        var sub = false;
+        for (var j = 0; j < hints.length; j++) {
+          if (hints[j].type.toString() === fieldType.toString()) {
+            sub = hints[j];
+            break;
+          }
+        }
+        if (sub) {
+          var subResult = decodeTypeDefsRec(sub, hints);
+          if (arrayLen === -1) {
+          }
+          else {
+            typeDefDict[fieldName] = [subResult];
+          }
+        }
+        else {
+          that.emit('error', 'Cannot find ' + fieldType + ' in decodeTypeDefs');
+        }
+      }
+    }
+    return typeDefDict;
+  };
+
+  return decodeTypeDefsRec(defs[0], defs);
+};
+
+
+module.exports = Ros;
+
+},{"./Service":13,"./ServiceRequest":14,"./SocketAdapter.js":16,"eventemitter2":1,"object-assign":2,"ws":39}],13:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author Brandon Alexander - baalexander@gmail.com
+ */
+
+var ServiceResponse = require('./ServiceResponse');
+var ServiceRequest = require('./ServiceRequest');
+var EventEmitter2 = require('eventemitter2').EventEmitter2;
+
+/**
+ * A ROS service client.
+ *
+ * @constructor
+ * @params options - possible keys include:
+ *   * ros - the ROSLIB.Ros connection handle
+ *   * name - the service name, like /add_two_ints
+ *   * serviceType - the service type, like 'rospy_tutorials/AddTwoInts'
+ */
+function Service(options) {
+  options = options || {};
+  this.ros = options.ros;
+  this.name = options.name;
+  this.serviceType = options.serviceType;
+  this.isAdvertised = false;
+
+  this._serviceCallback = null;
+}
+Service.prototype.__proto__ = EventEmitter2.prototype;
+/**
+ * Calls the service. Returns the service response in the callback.
+ *
+ * @param request - the ROSLIB.ServiceRequest to send
+ * @param callback - function with params:
+ *   * response - the response from the service request
+ * @param failedCallback - the callback function when the service call failed (optional). Params:
+ *   * error - the error message reported by ROS
+ */
+Service.prototype.callService = function(request, callback, failedCallback) {
+  if (this.isAdvertised) {
+    return;
+  }
+
+  var serviceCallId = 'call_service:' + this.name + ':' + (++this.ros.idCounter);
+
+  if (callback || failedCallback) {
+    this.ros.once(serviceCallId, function(message) {
+      if (message.result !== undefined && message.result === false) {
+        if (typeof failedCallback === 'function') {
+          failedCallback(message.values);
+        }
+      } else if (typeof callback === 'function') {
+        callback(new ServiceResponse(message.values));
+      }
+    });
+  }
+
+  var call = {
+    op : 'call_service',
+    id : serviceCallId,
+    service : this.name,
+    args : request
+  };
+  this.ros.callOnConnection(call);
+};
+
+/**
+ * Every time a message is published for the given topic, the callback
+ * will be called with the message object.
+ *
+ * @param callback - function with the following params:
+ *   * message - the published message
+ */
+Service.prototype.advertise = function(callback) {
+  if (this.isAdvertised || typeof callback !== 'function') {
+    return;
+  }
+
+  this._serviceCallback = callback;
+  this.ros.on(this.name, this._serviceResponse.bind(this));
+  this.ros.callOnConnection({
+    op: 'advertise_service',
+    type: this.serviceType,
+    service: this.name
+  });
+  this.isAdvertised = true;
+};
+
+Service.prototype.unadvertise = function() {
+  if (!this.isAdvertised) {
+    return;
+  }
+  this.ros.callOnConnection({
+    op: 'unadvertise_service',
+    service: this.name
+  });
+  this.isAdvertised = false;
+};
+
+Service.prototype._serviceResponse = function(rosbridgeRequest) {
+  var response = {};
+  var success = this._serviceCallback(rosbridgeRequest.args, response);
+
+  var call = {
+    op: 'service_response',
+    service: this.name,
+    values: new ServiceResponse(response),
+    result: success
+  };
+
+  if (rosbridgeRequest.id) {
+    call.id = rosbridgeRequest.id;
+  }
+
+  this.ros.callOnConnection(call);
+};
+
+module.exports = Service;
+},{"./ServiceRequest":14,"./ServiceResponse":15,"eventemitter2":1}],14:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author Brandon Alexander - balexander@willowgarage.com
+ */
+
+var assign = require('object-assign');
+
+/**
+ * A ServiceRequest is passed into the service call.
+ *
+ * @constructor
+ * @param values - object matching the fields defined in the .srv definition file
+ */
+function ServiceRequest(values) {
+  assign(this, values);
+}
+
+module.exports = ServiceRequest;
+},{"object-assign":2}],15:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author Brandon Alexander - balexander@willowgarage.com
+ */
+
+var assign = require('object-assign');
+
+/**
+ * A ServiceResponse is returned from the service call.
+ *
+ * @constructor
+ * @param values - object matching the fields defined in the .srv definition file
+ */
+function ServiceResponse(values) {
+  assign(this, values);
+}
+
+module.exports = ServiceResponse;
+},{"object-assign":2}],16:[function(require,module,exports){
+/**
+ * Socket event handling utilities for handling events on either
+ * WebSocket and TCP sockets
+ *
+ * Note to anyone reviewing this code: these functions are called
+ * in the context of their parent object, unless bound
+ * @fileOverview
+ */
+'use strict';
+
+var decompressPng = require('../util/decompressPng');
+var WebSocket = require('ws');
+var BSON = null;
+if(typeof bson !== 'undefined'){
+    BSON = bson().BSON;
+}
+
+/**
+ * Events listeners for a WebSocket or TCP socket to a JavaScript
+ * ROS Client. Sets up Messages for a given topic to trigger an
+ * event on the ROS client.
+ *
+ * @namespace SocketAdapter
+ * @private
+ */
+function SocketAdapter(client) {
+  function handleMessage(message) {
+    if (message.op === 'publish') {
+      client.emit(message.topic, message.msg);
+    } else if (message.op === 'service_response') {
+      client.emit(message.id, message);
+    } else if (message.op === 'call_service') {
+      client.emit(message.service, message);
+    } else if(message.op === 'status'){
+      if(message.id){
+        client.emit('status:'+message.id, message);
+      } else {
+        client.emit('status', message);
+      }
+    }
+  }
+
+  function handlePng(message, callback) {
+    if (message.op === 'png') {
+      decompressPng(message.data, callback);
+    } else {
+      callback(message);
+    }
+  }
+
+  function decodeBSON(data, callback) {
+    if (!BSON) {
+      throw 'Cannot process BSON encoded message without BSON header.';
+    }
+    var reader = new FileReader();
+    reader.onload  = function() {
+      var uint8Array = new Uint8Array(this.result);
+      var msg = BSON.deserialize(uint8Array);
+      callback(msg);
+    };
+    reader.readAsArrayBuffer(data);
+  }
+
+  return {
+    /**
+     * Emits a 'connection' event on WebSocket connection.
+     *
+     * @param event - the argument to emit with the event.
+     * @memberof SocketAdapter
+     */
+    onopen: function onOpen(event) {
+      client.isConnected = true;
+      client.emit('connection', event);
+    },
+
+    /**
+     * Emits a 'close' event on WebSocket disconnection.
+     *
+     * @param event - the argument to emit with the event.
+     * @memberof SocketAdapter
+     */
+    onclose: function onClose(event) {
+      client.isConnected = false;
+      client.emit('close', event);
+    },
+
+    /**
+     * Emits an 'error' event whenever there was an error.
+     *
+     * @param event - the argument to emit with the event.
+     * @memberof SocketAdapter
+     */
+    onerror: function onError(event) {
+      client.emit('error', event);
+    },
+
+    /**
+     * Parses message responses from rosbridge and sends to the appropriate
+     * topic, service, or param.
+     *
+     * @param message - the raw JSON message from rosbridge.
+     * @memberof SocketAdapter
+     */
+    onmessage: function onMessage(data) {
+      if (typeof Blob !== 'undefined' && data.data instanceof Blob) {
+        decodeBSON(data.data, function (message) {
+          handlePng(message, handleMessage);
+        });
+      } else {
+        var message = JSON.parse(typeof data === 'string' ? data : data.data);
+        handlePng(message, handleMessage);
+      }
+    }
+  };
+}
+
+module.exports = SocketAdapter;
+
+},{"../util/decompressPng":41,"ws":39}],17:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author Brandon Alexander - baalexander@gmail.com
+ */
+
+var EventEmitter2 = require('eventemitter2').EventEmitter2;
+var Message = require('./Message');
+
+/**
+ * Publish and/or subscribe to a topic in ROS.
+ *
+ * Emits the following events:
+ *  * 'warning' - if there are any warning during the Topic creation
+ *  * 'message' - the message data from rosbridge
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *   * ros - the ROSLIB.Ros connection handle
+ *   * name - the topic name, like /cmd_vel
+ *   * messageType - the message type, like 'std_msgs/String'
+ *   * compression - the type of compression to use, like 'png'
+ *   * throttle_rate - the rate (in ms in between messages) at which to throttle the topics
+ *   * queue_size - the queue created at bridge side for re-publishing webtopics (defaults to 100)
+ *   * latch - latch the topic when publishing
+ *   * queue_length - the queue length at bridge side used when subscribing (defaults to 0, no queueing).
+ *   * reconnect_on_close - the flag to enable resubscription and readvertisement on close event(defaults to true).
+ */
+function Topic(options) {
+  options = options || {};
+  this.ros = options.ros;
+  this.name = options.name;
+  this.messageType = options.messageType;
+  this.isAdvertised = false;
+  this.compression = options.compression || 'none';
+  this.throttle_rate = options.throttle_rate || 0;
+  this.latch = options.latch || false;
+  this.queue_size = options.queue_size || 100;
+  this.queue_length = options.queue_length || 0;
+  this.reconnect_on_close = options.reconnect_on_close || true;
+
+  // Check for valid compression types
+  if (this.compression && this.compression !== 'png' &&
+    this.compression !== 'none') {
+    this.emit('warning', this.compression +
+      ' compression is not supported. No compression will be used.');
+  }
+
+  // Check if throttle rate is negative
+  if (this.throttle_rate < 0) {
+    this.emit('warning', this.throttle_rate + ' is not allowed. Set to 0');
+    this.throttle_rate = 0;
+  }
+
+  var that = this;
+  if (this.reconnect_on_close) {
+    this.callForSubscribeAndAdvertise = function(message) {
+      that.ros.callOnConnection(message);
+
+      that.waitForReconnect = false;
+      that.reconnectFunc = function() {
+        if(!that.waitForReconnect) {
+          that.waitForReconnect = true;
+          that.ros.callOnConnection(message);
+          that.ros.once('connection', function() {
+            that.waitForReconnect = false;
+          });
+        }
+      };
+      that.ros.on('close', that.reconnectFunc);
+    };
+  }
+  else {
+    this.callForSubscribeAndAdvertise = this.ros.callOnConnection;
+  }
+
+  this._messageCallback = function(data) {
+    that.emit('message', new Message(data));
+  };
+}
+Topic.prototype.__proto__ = EventEmitter2.prototype;
+
+/**
+ * Every time a message is published for the given topic, the callback
+ * will be called with the message object.
+ *
+ * @param callback - function with the following params:
+ *   * message - the published message
+ */
+Topic.prototype.subscribe = function(callback) {
+  if (typeof callback === 'function') {
+    this.on('message', callback);
+  }
+
+  if (this.subscribeId) { return; }
+  this.ros.on(this.name, this._messageCallback);
+  this.subscribeId = 'subscribe:' + this.name + ':' + (++this.ros.idCounter);
+
+  this.callForSubscribeAndAdvertise({
+    op: 'subscribe',
+    id: this.subscribeId,
+    type: this.messageType,
+    topic: this.name,
+    compression: this.compression,
+    throttle_rate: this.throttle_rate,
+    queue_length: this.queue_length
+  });
+};
+
+/**
+ * Unregisters as a subscriber for the topic. Unsubscribing stop remove
+ * all subscribe callbacks. To remove a call back, you must explicitly
+ * pass the callback function in.
+ *
+ * @param callback - the optional callback to unregister, if
+ *     * provided and other listeners are registered the topic won't
+ *     * unsubscribe, just stop emitting to the passed listener
+ */
+Topic.prototype.unsubscribe = function(callback) {
+  if (callback) {
+    this.off('message', callback);
+    // If there is any other callbacks still subscribed don't unsubscribe
+    if (this.listeners('message').length) { return; }
+  }
+  if (!this.subscribeId) { return; }
+  // Note: Don't call this.removeAllListeners, allow client to handle that themselves
+  this.ros.off(this.name, this._messageCallback);
+  if(this.reconnect_on_close) {
+    this.ros.off('close', this.reconnectFunc);
+  }
+  this.emit('unsubscribe');
+  this.ros.callOnConnection({
+    op: 'unsubscribe',
+    id: this.subscribeId,
+    topic: this.name
+  });
+  this.subscribeId = null;
+};
+
+
+/**
+ * Registers as a publisher for the topic.
+ */
+Topic.prototype.advertise = function() {
+  if (this.isAdvertised) {
+    return;
+  }
+  this.advertiseId = 'advertise:' + this.name + ':' + (++this.ros.idCounter);
+  this.callForSubscribeAndAdvertise({
+    op: 'advertise',
+    id: this.advertiseId,
+    type: this.messageType,
+    topic: this.name,
+    latch: this.latch,
+    queue_size: this.queue_size
+  });
+  this.isAdvertised = true;
+
+  if(!this.reconnect_on_close) {
+    var that = this;
+    this.ros.on('close', function() {
+      that.isAdvertised = false;
+    });
+  }
+};
+
+/**
+ * Unregisters as a publisher for the topic.
+ */
+Topic.prototype.unadvertise = function() {
+  if (!this.isAdvertised) {
+    return;
+  }
+  if(this.reconnect_on_close) {
+    this.ros.off('close', this.reconnectFunc);
+  }
+  this.emit('unadvertise');
+  this.ros.callOnConnection({
+    op: 'unadvertise',
+    id: this.advertiseId,
+    topic: this.name
+  });
+  this.isAdvertised = false;
+};
+
+/**
+ * Publish the message.
+ *
+ * @param message - A ROSLIB.Message object.
+ */
+Topic.prototype.publish = function(message) {
+  if (!this.isAdvertised) {
+    this.advertise();
+  }
+
+  this.ros.idCounter++;
+  var call = {
+    op: 'publish',
+    id: 'publish:' + this.name + ':' + this.ros.idCounter,
+    topic: this.name,
+    msg: message,
+    latch: this.latch
+  };
+  this.ros.callOnConnection(call);
+};
+
+module.exports = Topic;
+
+},{"./Message":10,"eventemitter2":1}],18:[function(require,module,exports){
+var mixin = require('../mixin');
+
+var core = module.exports = {
+    Ros: require('./Ros'),
+    Topic: require('./Topic'),
+    Message: require('./Message'),
+    Param: require('./Param'),
+    Service: require('./Service'),
+    ServiceRequest: require('./ServiceRequest'),
+    ServiceResponse: require('./ServiceResponse')
+};
+
+mixin(core.Ros, ['Param', 'Service', 'Topic'], core);
+
+},{"../mixin":24,"./Message":10,"./Param":11,"./Ros":12,"./Service":13,"./ServiceRequest":14,"./ServiceResponse":15,"./Topic":17}],19:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author David Gossow - dgossow@willowgarage.com
+ */
+
+var Vector3 = require('./Vector3');
+var Quaternion = require('./Quaternion');
+
+/**
+ * A Pose in 3D space. Values are copied into this object.
+ *
+ *  @constructor
+ *  @param options - object with following keys:
+ *   * position - the Vector3 describing the position
+ *   * orientation - the ROSLIB.Quaternion describing the orientation
+ */
+function Pose(options) {
+  options = options || {};
+  // copy the values into this object if they exist
+  this.position = new Vector3(options.position);
+  this.orientation = new Quaternion(options.orientation);
+}
+
+/**
+ * Apply a transform against this pose.
+ *
+ * @param tf the transform
+ */
+Pose.prototype.applyTransform = function(tf) {
+  this.position.multiplyQuaternion(tf.rotation);
+  this.position.add(tf.translation);
+  var tmp = tf.rotation.clone();
+  tmp.multiply(this.orientation);
+  this.orientation = tmp;
+};
+
+/**
+ * Clone a copy of this pose.
+ *
+ * @returns the cloned pose
+ */
+Pose.prototype.clone = function() {
+  return new Pose(this);
+};
+
+module.exports = Pose;
+},{"./Quaternion":20,"./Vector3":22}],20:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author David Gossow - dgossow@willowgarage.com
+ */
+
+/**
+ * A Quaternion.
+ *
+ *  @constructor
+ *  @param options - object with following keys:
+ *   * x - the x value
+ *   * y - the y value
+ *   * z - the z value
+ *   * w - the w value
+ */
+function Quaternion(options) {
+  options = options || {};
+  this.x = options.x || 0;
+  this.y = options.y || 0;
+  this.z = options.z || 0;
+  this.w = (typeof options.w === 'number') ? options.w : 1;
+}
+
+/**
+ * Perform a conjugation on this quaternion.
+ */
+Quaternion.prototype.conjugate = function() {
+  this.x *= -1;
+  this.y *= -1;
+  this.z *= -1;
+};
+
+/**
+ * Return the norm of this quaternion.
+ */
+Quaternion.prototype.norm = function() {
+  return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
+};
+
+/**
+ * Perform a normalization on this quaternion.
+ */
+Quaternion.prototype.normalize = function() {
+  var l = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
+  if (l === 0) {
+    this.x = 0;
+    this.y = 0;
+    this.z = 0;
+    this.w = 1;
+  } else {
+    l = 1 / l;
+    this.x = this.x * l;
+    this.y = this.y * l;
+    this.z = this.z * l;
+    this.w = this.w * l;
+  }
+};
+
+/**
+ * Convert this quaternion into its inverse.
+ */
+Quaternion.prototype.invert = function() {
+  this.conjugate();
+  this.normalize();
+};
+
+/**
+ * Set the values of this quaternion to the product of itself and the given quaternion.
+ *
+ * @param q the quaternion to multiply with
+ */
+Quaternion.prototype.multiply = function(q) {
+  var newX = this.x * q.w + this.y * q.z - this.z * q.y + this.w * q.x;
+  var newY = -this.x * q.z + this.y * q.w + this.z * q.x + this.w * q.y;
+  var newZ = this.x * q.y - this.y * q.x + this.z * q.w + this.w * q.z;
+  var newW = -this.x * q.x - this.y * q.y - this.z * q.z + this.w * q.w;
+  this.x = newX;
+  this.y = newY;
+  this.z = newZ;
+  this.w = newW;
+};
+
+/**
+ * Clone a copy of this quaternion.
+ *
+ * @returns the cloned quaternion
+ */
+Quaternion.prototype.clone = function() {
+  return new Quaternion(this);
+};
+
+module.exports = Quaternion;
+
+},{}],21:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author David Gossow - dgossow@willowgarage.com
+ */
+
+var Vector3 = require('./Vector3');
+var Quaternion = require('./Quaternion');
+
+/**
+ * A Transform in 3-space. Values are copied into this object.
+ *
+ *  @constructor
+ *  @param options - object with following keys:
+ *   * translation - the Vector3 describing the translation
+ *   * rotation - the ROSLIB.Quaternion describing the rotation
+ */
+function Transform(options) {
+  options = options || {};
+  // Copy the values into this object if they exist
+  this.translation = new Vector3(options.translation);
+  this.rotation = new Quaternion(options.rotation);
+}
+
+/**
+ * Clone a copy of this transform.
+ *
+ * @returns the cloned transform
+ */
+Transform.prototype.clone = function() {
+  return new Transform(this);
+};
+
+module.exports = Transform;
+},{"./Quaternion":20,"./Vector3":22}],22:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author David Gossow - dgossow@willowgarage.com
+ */
+
+/**
+ * A 3D vector.
+ *
+ *  @constructor
+ *  @param options - object with following keys:
+ *   * x - the x value
+ *   * y - the y value
+ *   * z - the z value
+ */
+function Vector3(options) {
+  options = options || {};
+  this.x = options.x || 0;
+  this.y = options.y || 0;
+  this.z = options.z || 0;
+}
+
+/**
+ * Set the values of this vector to the sum of itself and the given vector.
+ *
+ * @param v the vector to add with
+ */
+Vector3.prototype.add = function(v) {
+  this.x += v.x;
+  this.y += v.y;
+  this.z += v.z;
+};
+
+/**
+ * Set the values of this vector to the difference of itself and the given vector.
+ *
+ * @param v the vector to subtract with
+ */
+Vector3.prototype.subtract = function(v) {
+  this.x -= v.x;
+  this.y -= v.y;
+  this.z -= v.z;
+};
+
+/**
+ * Multiply the given Quaternion with this vector.
+ *
+ * @param q - the quaternion to multiply with
+ */
+Vector3.prototype.multiplyQuaternion = function(q) {
+  var ix = q.w * this.x + q.y * this.z - q.z * this.y;
+  var iy = q.w * this.y + q.z * this.x - q.x * this.z;
+  var iz = q.w * this.z + q.x * this.y - q.y * this.x;
+  var iw = -q.x * this.x - q.y * this.y - q.z * this.z;
+  this.x = ix * q.w + iw * -q.x + iy * -q.z - iz * -q.y;
+  this.y = iy * q.w + iw * -q.y + iz * -q.x - ix * -q.z;
+  this.z = iz * q.w + iw * -q.z + ix * -q.y - iy * -q.x;
+};
+
+/**
+ * Clone a copy of this vector.
+ *
+ * @returns the cloned vector
+ */
+Vector3.prototype.clone = function() {
+  return new Vector3(this);
+};
+
+module.exports = Vector3;
+},{}],23:[function(require,module,exports){
+module.exports = {
+    Pose: require('./Pose'),
+    Quaternion: require('./Quaternion'),
+    Transform: require('./Transform'),
+    Vector3: require('./Vector3')
+};
+
+},{"./Pose":19,"./Quaternion":20,"./Transform":21,"./Vector3":22}],24:[function(require,module,exports){
+/**
+ * Mixin a feature to the core/Ros prototype.
+ * For example, mixin(Ros, ['Topic'], {Topic: <Topic>})
+ * will add a topic bound to any Ros instances so a user
+ * can call `var topic = ros.Topic({name: '/foo'});`
+ *
+ * @author Graeme Yeates - github.com/megawac
+ */
+module.exports = function(Ros, classes, features) {
+    classes.forEach(function(className) {
+        var Class = features[className];
+        Ros.prototype[className] = function(options) {
+            options.ros = this;
+            return new Class(options);
+        };
+    });
+};
+
+},{}],25:[function(require,module,exports){
+/**
+ * @fileoverview
+ * @author David Gossow - dgossow@willowgarage.com
+ */
+
+var ActionClient = require('../actionlib/ActionClient');
+var Goal = require('../actionlib/Goal');
+
+var Service = require('../core/Service.js');
+var ServiceRequest = require('../core/ServiceRequest.js');
+
+var Transform = require('../math/Transform');
+
+/**
+ * A TF Client that listens to TFs from tf2_web_republisher.
+ *
+ *  @constructor
+ *  @param options - object with following keys:
+ *   * ros - the ROSLIB.Ros connection handle
+ *   * fixedFrame - the fixed frame, like /base_link
+ *   * angularThres - the angular threshold for the TF republisher
+ *   * transThres - the translation threshold for the TF republisher
+ *   * rate - the rate for the TF republisher
+ *   * updateDelay - the time (in ms) to wait after a new subscription
+ *                   to update the TF republisher's list of TFs
+ *   * topicTimeout - the timeout parameter for the TF republisher
+ *   * serverName (optional) - the name of the tf2_web_republisher server
+ *   * repubServiceName (optional) - the name of the republish_tfs service (non groovy compatibility mode only)
+ *                                                                                                                              default: '/republish_tfs'
+ */
+function TFClient(options) {
+  options = options || {};
+  this.ros = options.ros;
+  this.fixedFrame = options.fixedFrame || '/base_link';
+  this.angularThres = options.angularThres || 2.0;
+  this.transThres = options.transThres || 0.01;
+  this.rate = options.rate || 10.0;
+  this.updateDelay = options.updateDelay || 50;
+  var seconds = options.topicTimeout || 2.0;
+  var secs = Math.floor(seconds);
+  var nsecs = Math.floor((seconds - secs) * 1000000000);
+  this.topicTimeout = {
+    secs: secs,
+    nsecs: nsecs
+  };
+  this.serverName = options.serverName || '/tf2_web_republisher';
+  this.repubServiceName = options.repubServiceName || '/republish_tfs';
+
+  this.currentGoal = false;
+  this.currentTopic = false;
+  this.frameInfos = {};
+  this.republisherUpdateRequested = false;
+
+  // Create an Action client
+  this.actionClient = this.ros.ActionClient({
+    serverName : this.serverName,
+    actionName : 'tf2_web_republisher/TFSubscriptionAction',
+    omitStatus : true,
+    omitResult : true
+  });
+
+  // Create a Service client
+  this.serviceClient = this.ros.Service({
+    name: this.repubServiceName,
+    serviceType: 'tf2_web_republisher/RepublishTFs'
+  });
+}
+
+/**
+ * Process the incoming TF message and send them out using the callback
+ * functions.
+ *
+ * @param tf - the TF message from the server
+ */
+TFClient.prototype.processTFArray = function(tf) {
+  var that = this;
+  tf.transforms.forEach(function(transform) {
+    var frameID = transform.child_frame_id;
+    if (frameID[0] === '/')
+    {
+      frameID = frameID.substring(1);
+    }
+    var info = this.frameInfos[frameID];
+    if (info) {
+      info.transform = new Transform({
+        translation : transform.transform.translation,
+        rotation : transform.transform.rotation
+      });
+      info.cbs.forEach(function(cb) {
+        cb(info.transform);
+      });
+    }
+  }, this);
+};
+
+/**
+ * Create and send a new goal (or service request) to the tf2_web_republisher
+ * based on the current list of TFs.
+ */
+TFClient.prototype.updateGoal = function() {
+  var goalMessage = {
+    source_frames : Object.keys(this.frameInfos),
+    target_frame : this.fixedFrame,
+    angular_thres : this.angularThres,
+    trans_thres : this.transThres,
+    rate : this.rate
+  };
+
+  // if we're running in groovy compatibility mode (the default)
+  // then use the action interface to tf2_web_republisher
+  if(this.ros.groovyCompatibility) {
+    if (this.currentGoal) {
+      this.currentGoal.cancel();
+    }
+    this.currentGoal = new Goal({
+      actionClient : this.actionClient,
+      goalMessage : goalMessage
+    });
+
+    this.currentGoal.on('feedback', this.processTFArray.bind(this));
+    this.currentGoal.send();
+  }
+  else {
+    // otherwise, use the service interface
+    // The service interface has the same parameters as the action,
+    // plus the timeout
+    goalMessage.timeout = this.topicTimeout;
+    var request = new ServiceRequest(goalMessage);
+
+    this.serviceClient.callService(request, this.processResponse.bind(this));
+  }
+
+  this.republisherUpdateRequested = false;
+};
+
+/**
+ * Process the service response and subscribe to the tf republisher
+ * topic
+ *
+ * @param response the service response containing the topic name
+ */
+TFClient.prototype.processResponse = function(response) {
+  // if we subscribed to a topic before, unsubscribe so
+  // the republisher stops publishing it
+  if (this.currentTopic) {
+    this.currentTopic.unsubscribe();
+  }
+
+  this.currentTopic = this.ros.Topic({
+    name: response.topic_name,
+    messageType: 'tf2_web_republisher/TFArray'
+  });
+  this.currentTopic.subscribe(this.processTFArray.bind(this));
+};
+
+/**
+ * Subscribe to the given TF frame.
+ *
+ * @param frameID - the TF frame to subscribe to
+ * @param callback - function with params:
+ *   * transform - the transform data
+ */
+TFClient.prototype.subscribe = function(frameID, callback) {
+  // remove leading slash, if it's there
+  if (frameID[0] === '/')
+  {
+    frameID = frameID.substring(1);
+  }
+  // if there is no callback registered for the given frame, create emtpy callback list
+  if (!this.frameInfos[frameID]) {
+    this.frameInfos[frameID] = {
+      cbs: []
+    };
+    if (!this.republisherUpdateRequested) {
+      setTimeout(this.updateGoal.bind(this), this.updateDelay);
+      this.republisherUpdateRequested = true;
+    }
+  }
+  // if we already have a transform, call back immediately
+  else if (this.frameInfos[frameID].transform) {
+    callback(this.frameInfos[frameID].transform);
+  }
+  this.frameInfos[frameID].cbs.push(callback);
+};
+
+/**
+ * Unsubscribe from the given TF frame.
+ *
+ * @param frameID - the TF frame to unsubscribe from
+ * @param callback - the callback function to remove
+ */
+TFClient.prototype.unsubscribe = function(frameID, callback) {
+  // remove leading slash, if it's there
+  if (frameID[0] === '/')
+  {
+    frameID = frameID.substring(1);
+  }
+  var info = this.frameInfos[frameID];
+  for (var cbs = info && info.cbs || [], idx = cbs.length; idx--;) {
+    if (cbs[idx] === callback) {
+      cbs.splice(idx, 1);
+    }
+  }
+  if (!callback || cbs.length === 0) {
+    delete this.frameInfos[frameID];
+  }
+};
+
+/**
+ * Unsubscribe and unadvertise all topics associated with this TFClient.
+ */
+TFClient.prototype.dispose = function() {
+  this.actionClient.dispose();
+  if (this.currentTopic) {
+    this.currentTopic.unsubscribe();
+  }
+};
+
+module.exports = TFClient;
+
+},{"../actionlib/ActionClient":5,"../actionlib/Goal":7,"../core/Service.js":13,"../core/ServiceRequest.js":14,"../math/Transform":21}],26:[function(require,module,exports){
+var Ros = require('../core/Ros');
+var mixin = require('../mixin');
+
+var tf = module.exports = {
+    TFClient: require('./TFClient')
+};
+
+mixin(Ros, ['TFClient'], tf);
+},{"../core/Ros":12,"../mixin":24,"./TFClient":25}],27:[function(require,module,exports){
+/**
+ * @fileOverview 
+ * @author Benjamin Pitzer - ben.pitzer@gmail.com
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var Vector3 = require('../math/Vector3');
+var UrdfTypes = require('./UrdfTypes');
+
+/**
+ * A Box element in a URDF.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ */
+function UrdfBox(options) {
+  this.dimension = null;
+  this.type = UrdfTypes.URDF_BOX;
+
+  // Parse the xml string
+  var xyz = options.xml.getAttribute('size').split(' ');
+  this.dimension = new Vector3({
+    x : parseFloat(xyz[0]),
+    y : parseFloat(xyz[1]),
+    z : parseFloat(xyz[2])
+  });
+}
+
+module.exports = UrdfBox;
+},{"../math/Vector3":22,"./UrdfTypes":36}],28:[function(require,module,exports){
+/**
+ * @fileOverview 
+ * @author Benjamin Pitzer - ben.pitzer@gmail.com
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+/**
+ * A Color element in a URDF.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ */
+function UrdfColor(options) {
+  // Parse the xml string
+  var rgba = options.xml.getAttribute('rgba').split(' ');
+  this.r = parseFloat(rgba[0]);
+  this.g = parseFloat(rgba[1]);
+  this.b = parseFloat(rgba[2]);
+  this.a = parseFloat(rgba[3]);
+}
+
+module.exports = UrdfColor;
+},{}],29:[function(require,module,exports){
+/**
+ * @fileOverview 
+ * @author Benjamin Pitzer - ben.pitzer@gmail.com
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var UrdfTypes = require('./UrdfTypes');
+
+/**
+ * A Cylinder element in a URDF.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ */
+function UrdfCylinder(options) {
+  this.type = UrdfTypes.URDF_CYLINDER;
+  this.length = parseFloat(options.xml.getAttribute('length'));
+  this.radius = parseFloat(options.xml.getAttribute('radius'));
+}
+
+module.exports = UrdfCylinder;
+},{"./UrdfTypes":36}],30:[function(require,module,exports){
+/**
+ * @fileOverview
+ * @author David V. Lu!!  davidvlu@gmail.com
+ */
+
+/**
+ * A Joint element in a URDF.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ */
+function UrdfJoint(options) {
+  this.name = options.xml.getAttribute('name');
+  this.type = options.xml.getAttribute('type');
+
+  var parents = options.xml.getElementsByTagName('parent');
+  if(parents.length > 0) {
+    this.parent = parents[0].getAttribute('link');
+  }
+
+  var children = options.xml.getElementsByTagName('child');
+  if(children.length > 0) {
+    this.child = children[0].getAttribute('link');
+  }
+
+  var limits = options.xml.getElementsByTagName('limit');
+  if (limits.length > 0) {
+    this.minval = parseFloat( limits[0].getAttribute('lower') );
+    this.maxval = parseFloat( limits[0].getAttribute('upper') );
+  }
+}
+
+module.exports = UrdfJoint;
+
+},{}],31:[function(require,module,exports){
+/**
+ * @fileOverview 
+ * @author Benjamin Pitzer - ben.pitzer@gmail.com
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var UrdfVisual = require('./UrdfVisual');
+
+/**
+ * A Link element in a URDF.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ */
+function UrdfLink(options) {
+  this.name = options.xml.getAttribute('name');
+  this.visuals = [];
+  var visuals = options.xml.getElementsByTagName('visual');
+
+  for( var i=0; i<visuals.length; i++ ) {
+    this.visuals.push( new UrdfVisual({
+      xml : visuals[i]
+    }) );
+  }
+}
+
+module.exports = UrdfLink;
+},{"./UrdfVisual":37}],32:[function(require,module,exports){
+/**
+ * @fileOverview 
+ * @author Benjamin Pitzer - ben.pitzer@gmail.com
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var UrdfColor = require('./UrdfColor');
+
+/**
+ * A Material element in a URDF.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ */
+function UrdfMaterial(options) {
+  this.textureFilename = null;
+  this.color = null;
+
+  this.name = options.xml.getAttribute('name');
+
+  // Texture
+  var textures = options.xml.getElementsByTagName('texture');
+  if (textures.length > 0) {
+    this.textureFilename = textures[0].getAttribute('filename');
+  }
+
+  // Color
+  var colors = options.xml.getElementsByTagName('color');
+  if (colors.length > 0) {
+    // Parse the RBGA string
+    this.color = new UrdfColor({
+      xml : colors[0]
+    });
+  }
+}
+
+UrdfMaterial.prototype.isLink = function() {
+  return this.color === null && this.textureFilename === null;
+};
+
+var assign = require('object-assign');
+
+UrdfMaterial.prototype.assign = function(obj) {
+    return assign(this, obj);
+};
+
+module.exports = UrdfMaterial;
+
+},{"./UrdfColor":28,"object-assign":2}],33:[function(require,module,exports){
+/**
+ * @fileOverview 
+ * @author Benjamin Pitzer - ben.pitzer@gmail.com
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var Vector3 = require('../math/Vector3');
+var UrdfTypes = require('./UrdfTypes');
+
+/**
+ * A Mesh element in a URDF.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ */
+function UrdfMesh(options) {
+  this.scale = null;
+
+  this.type = UrdfTypes.URDF_MESH;
+  this.filename = options.xml.getAttribute('filename');
+
+  // Check for a scale
+  var scale = options.xml.getAttribute('scale');
+  if (scale) {
+    // Get the XYZ
+    var xyz = scale.split(' ');
+    this.scale = new Vector3({
+      x : parseFloat(xyz[0]),
+      y : parseFloat(xyz[1]),
+      z : parseFloat(xyz[2])
+    });
+  }
+}
+
+module.exports = UrdfMesh;
+},{"../math/Vector3":22,"./UrdfTypes":36}],34:[function(require,module,exports){
+/**
+ * @fileOverview 
+ * @author Benjamin Pitzer - ben.pitzer@gmail.com
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var UrdfMaterial = require('./UrdfMaterial');
+var UrdfLink = require('./UrdfLink');
+var UrdfJoint = require('./UrdfJoint');
+var DOMParser = require('xmldom').DOMParser;
+
+// See https://developer.mozilla.org/docs/XPathResult#Constants
+var XPATH_FIRST_ORDERED_NODE_TYPE = 9;
+
+/**
+ * A URDF Model can be used to parse a given URDF into the appropriate elements.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ *  * string - the XML element to parse as a string
+ */
+function UrdfModel(options) {
+  options = options || {};
+  var xmlDoc = options.xml;
+  var string = options.string;
+  this.materials = {};
+  this.links = {};
+  this.joints = {};
+
+  // Check if we are using a string or an XML element
+  if (string) {
+    // Parse the string
+    var parser = new DOMParser();
+    xmlDoc = parser.parseFromString(string, 'text/xml');
+  }
+
+  // Initialize the model with the given XML node.
+  // Get the robot tag
+  var robotXml = xmlDoc.documentElement;
+
+  // Get the robot name
+  this.name = robotXml.getAttribute('name');
+
+  // Parse all the visual elements we need
+  for (var nodes = robotXml.childNodes, i = 0; i < nodes.length; i++) {
+    var node = nodes[i];
+    if (node.tagName === 'material') {
+      var material = new UrdfMaterial({
+        xml : node
+      });
+      // Make sure this is unique
+      if (this.materials[material.name] !== void 0) {
+        if( this.materials[material.name].isLink() ) {
+          this.materials[material.name].assign( material );
+        } else {
+          console.warn('Material ' + material.name + 'is not unique.');
+        }
+      } else {
+        this.materials[material.name] = material;
+      }
+    } else if (node.tagName === 'link') {
+      var link = new UrdfLink({
+        xml : node
+      });
+      // Make sure this is unique
+      if (this.links[link.name] !== void 0) {
+        console.warn('Link ' + link.name + ' is not unique.');
+      } else {
+        // Check for a material
+        for( var j=0; j<link.visuals.length; j++ )
+        {
+          var mat = link.visuals[j].material; 
+          if ( mat !== null ) {
+            if (this.materials[mat.name] !== void 0) {
+              link.visuals[j].material = this.materials[mat.name];
+            } else {
+              this.materials[mat.name] = mat;
+            }
+          }
+        }
+
+        // Add the link
+        this.links[link.name] = link;
+      }
+    } else if (node.tagName === 'joint') {
+      var joint = new UrdfJoint({
+        xml : node
+      });
+      this.joints[joint.name] = joint;
+    }
+  }
+}
+
+module.exports = UrdfModel;
+
+},{"./UrdfJoint":30,"./UrdfLink":31,"./UrdfMaterial":32,"xmldom":42}],35:[function(require,module,exports){
+/**
+ * @fileOverview 
+ * @author Benjamin Pitzer - ben.pitzer@gmail.com
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var UrdfTypes = require('./UrdfTypes');
+
+/**
+ * A Sphere element in a URDF.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ */
+function UrdfSphere(options) {
+  this.type = UrdfTypes.URDF_SPHERE;
+  this.radius = parseFloat(options.xml.getAttribute('radius'));
+}
+
+module.exports = UrdfSphere;
+},{"./UrdfTypes":36}],36:[function(require,module,exports){
+module.exports = {
+       URDF_SPHERE : 0,
+       URDF_BOX : 1,
+       URDF_CYLINDER : 2,
+       URDF_MESH : 3
+};
+
+},{}],37:[function(require,module,exports){
+/**
+ * @fileOverview 
+ * @author Benjamin Pitzer - ben.pitzer@gmail.com
+ * @author Russell Toris - rctoris@wpi.edu
+ */
+
+var Pose = require('../math/Pose');
+var Vector3 = require('../math/Vector3');
+var Quaternion = require('../math/Quaternion');
+
+var UrdfCylinder = require('./UrdfCylinder');
+var UrdfBox = require('./UrdfBox');
+var UrdfMaterial = require('./UrdfMaterial');
+var UrdfMesh = require('./UrdfMesh');
+var UrdfSphere = require('./UrdfSphere');
+
+/**
+ * A Visual element in a URDF.
+ *
+ * @constructor
+ * @param options - object with following keys:
+ *  * xml - the XML element to parse
+ */
+function UrdfVisual(options) {
+  var xml = options.xml;
+  this.origin = null;
+  this.geometry = null;
+  this.material = null;
+
+  // Origin
+  var origins = xml.getElementsByTagName('origin');
+  if (origins.length === 0) {
+    // use the identity as the default
+    this.origin = new Pose();
+  } else {
+    // Check the XYZ
+    var xyz = origins[0].getAttribute('xyz');
+    var position = new Vector3();
+    if (xyz) {
+      xyz = xyz.split(' ');
+      position = new Vector3({
+        x : parseFloat(xyz[0]),
+        y : parseFloat(xyz[1]),
+        z : parseFloat(xyz[2])
+      });
+    }
+
+    // Check the RPY
+    var rpy = origins[0].getAttribute('rpy');
+    var orientation = new Quaternion();
+    if (rpy) {
+      rpy = rpy.split(' ');
+      // Convert from RPY
+      var roll = parseFloat(rpy[0]);
+      var pitch = parseFloat(rpy[1]);
+      var yaw = parseFloat(rpy[2]);
+      var phi = roll / 2.0;
+      var the = pitch / 2.0;
+      var psi = yaw / 2.0;
+      var x = Math.sin(phi) * Math.cos(the) * Math.cos(psi) - Math.cos(phi) * Math.sin(the)
+          * Math.sin(psi);
+      var y = Math.cos(phi) * Math.sin(the) * Math.cos(psi) + Math.sin(phi) * Math.cos(the)
+          * Math.sin(psi);
+      var z = Math.cos(phi) * Math.cos(the) * Math.sin(psi) - Math.sin(phi) * Math.sin(the)
+          * Math.cos(psi);
+      var w = Math.cos(phi) * Math.cos(the) * Math.cos(psi) + Math.sin(phi) * Math.sin(the)
+          * Math.sin(psi);
+
+      orientation = new Quaternion({
+        x : x,
+        y : y,
+        z : z,
+        w : w
+      });
+      orientation.normalize();
+    }
+    this.origin = new Pose({
+      position : position,
+      orientation : orientation
+    });
+  }
+
+  // Geometry
+  var geoms = xml.getElementsByTagName('geometry');
+  if (geoms.length > 0) {
+    var geom = geoms[0];
+    var shape = null;
+    // Check for the shape
+    for (var i = 0; i < geom.childNodes.length; i++) {
+      var node = geom.childNodes[i];
+      if (node.nodeType === 1) {
+        shape = node;
+        break;
+      }
+    }
+    // Check the type
+    var type = shape.nodeName;
+    if (type === 'sphere') {
+      this.geometry = new UrdfSphere({
+        xml : shape
+      });
+    } else if (type === 'box') {
+      this.geometry = new UrdfBox({
+        xml : shape
+      });
+    } else if (type === 'cylinder') {
+      this.geometry = new UrdfCylinder({
+        xml : shape
+      });
+    } else if (type === 'mesh') {
+      this.geometry = new UrdfMesh({
+        xml : shape
+      });
+    } else {
+      console.warn('Unknown geometry type ' + type);
+    }
+  }
+
+  // Material
+  var materials = xml.getElementsByTagName('material');
+  if (materials.length > 0) {
+    this.material = new UrdfMaterial({
+      xml : materials[0]
+    });
+  }
+}
+
+module.exports = UrdfVisual;
+},{"../math/Pose":19,"../math/Quaternion":20,"../math/Vector3":22,"./UrdfBox":27,"./UrdfCylinder":29,"./UrdfMaterial":32,"./UrdfMesh":33,"./UrdfSphere":35}],38:[function(require,module,exports){
+module.exports = require('object-assign')({
+    UrdfBox: require('./UrdfBox'),
+    UrdfColor: require('./UrdfColor'),
+    UrdfCylinder: require('./UrdfCylinder'),
+    UrdfLink: require('./UrdfLink'),
+    UrdfMaterial: require('./UrdfMaterial'),
+    UrdfMesh: require('./UrdfMesh'),
+    UrdfModel: require('./UrdfModel'),
+    UrdfSphere: require('./UrdfSphere'),
+    UrdfVisual: require('./UrdfVisual')
+}, require('./UrdfTypes'));
+
+},{"./UrdfBox":27,"./UrdfColor":28,"./UrdfCylinder":29,"./UrdfLink":31,"./UrdfMaterial":32,"./UrdfMesh":33,"./UrdfModel":34,"./UrdfSphere":35,"./UrdfTypes":36,"./UrdfVisual":37,"object-assign":2}],39:[function(require,module,exports){
+(function (global){
+module.exports = global.WebSocket;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],40:[function(require,module,exports){
+/* global document */
+module.exports = function Canvas() {
+       return document.createElement('canvas');
+};
+},{}],41:[function(require,module,exports){
+(function (global){
+/**
+ * @fileOverview
+ * @author Graeme Yeates - github.com/megawac
+ */
+
+'use strict';
+
+var Canvas = require('canvas');
+var Image = Canvas.Image || global.Image;
+
+/**
+ * If a message was compressed as a PNG image (a compression hack since
+ * gzipping over WebSockets * is not supported yet), this function places the
+ * "image" in a canvas element then decodes the * "image" as a Base64 string.
+ *
+ * @private
+ * @param data - object containing the PNG data.
+ * @param callback - function with params:
+ *   * data - the uncompressed data
+ */
+function decompressPng(data, callback) {
+  // Uncompresses the data before sending it through (use image/canvas to do so).
+  var image = new Image();
+  // When the image loads, extracts the raw data (JSON message).
+  image.onload = function() {
+    // Creates a local canvas to draw on.
+    var canvas = new Canvas();
+    var context = canvas.getContext('2d');
+
+    // Sets width and height.
+    canvas.width = image.width;
+    canvas.height = image.height;
+
+    // Prevents anti-aliasing and loosing data
+    context.imageSmoothingEnabled = false;
+    context.webkitImageSmoothingEnabled = false;
+    context.mozImageSmoothingEnabled = false;
+
+    // Puts the data into the image.
+    context.drawImage(image, 0, 0);
+    // Grabs the raw, uncompressed data.
+    var imageData = context.getImageData(0, 0, image.width, image.height).data;
+
+    // Constructs the JSON.
+    var jsonData = '';
+    for (var i = 0; i < imageData.length; i += 4) {
+      // RGB
+      jsonData += String.fromCharCode(imageData[i], imageData[i + 1], imageData[i + 2]);
+    }
+    callback(JSON.parse(jsonData));
+  };
+  // Sends the image data to load.
+  image.src = 'data:image/png;base64,' + data;
+}
+
+module.exports = decompressPng;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"canvas":40}],42:[function(require,module,exports){
+(function (global){
+exports.DOMImplementation = global.DOMImplementation;
+exports.XMLSerializer = global.XMLSerializer;
+exports.DOMParser = global.DOMParser;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}]},{},[4]);