You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by sg...@apache.org on 2016/03/25 16:39:28 UTC

[1/3] cordova-paramedic git commit: Adding real time logging and other improvements

Repository: cordova-paramedic
Updated Branches:
  refs/heads/master 8615f31c9 -> b1aa699df


http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/paramedic.js
----------------------------------------------------------------------
diff --git a/paramedic.js b/paramedic.js
deleted file mode 100644
index f811984..0000000
--- a/paramedic.js
+++ /dev/null
@@ -1,305 +0,0 @@
-#!/usr/bin/env node
-
-var http = require('http'),
-    localtunnel = require('localtunnel'),
-    parseArgs = require('minimist'),
-    shell = require('shelljs'),
-    fs = require('fs'),
-    request = require('request'),
-    tmp = require('tmp'),
-    path = require('path');
-
-var PORT = 8008;
-var TIMEOUT = 10 * 60 * 1000; // 10 minutes in msec - this will become a param
-
-
-
-function ParamedicRunner(_platformId,_plugins,_callback,bJustBuild,nPort,msTimeout,browserify,bSilent,bVerbose,platformPath) {
-    this.tunneledUrl = "";
-    this.port = nPort;
-    this.justBuild = bJustBuild;
-    this.plugins = _plugins;
-    this.platformId = _platformId;
-    this.callback = _callback;
-    this.tempFolder = null;
-    this.timeout = msTimeout;
-    this.verbose = bVerbose;
-    this.platformPath = platformPath;
-
-    if(browserify) {
-        this.browserify = "--browserify";
-    } else {
-        this.browserify = '';
-    }
-    
-    if(bSilent) {
-        var logOutput = this.logOutput = [];
-        this.logMessage = function(msg) {
-            logOutput.push(msg);
-        };
-    }
-    else {
-        this.logMessage = function(msg) {
-            console.log(msg);
-        };
-    }
-}
-
-ParamedicRunner.prototype = {
-    run: function() {
-        var cordovaResult = shell.exec('cordova --version', {silent:!this.verbose});
-        if(cordovaResult.code) {
-            this.logMessage(cordovaResult.output);
-            // this would be fatal
-            process.exit(cordovaResult.code);
-        }
-
-        // limit runtime to TIMEOUT msecs
-        var self = this;
-        setTimeout(function(){
-            self.logMessage("This test seems to be blocked :: timeout exceeded. Exiting ...");
-            self.cleanUpAndExitWithCode(1);
-        },self.timeout);
-
-        this.createTempProject();
-        this.installPlugins();
-        this.startServer();
-    },
-    createTempProject: function() {
-        this.tempFolder = tmp.dirSync();
-        tmp.setGracefulCleanup();
-        this.logMessage("cordova-paramedic: creating temp project at " + this.tempFolder.name);
-        shell.exec('cordova create ' + this.tempFolder.name,{silent:!this.verbose});
-        shell.cd(this.tempFolder.name);
-    },
-    installSinglePlugin: function(plugin) {
-        this.logMessage("cordova-paramedic: installing " + plugin);
-        var pluginPath = path.resolve(this.storedCWD, plugin);
-        var plugAddCmd = shell.exec('cordova plugin add ' + pluginPath, {silent:!this.verbose});
-        if(plugAddCmd.code !== 0) {
-            this.logMessage('Failed to install plugin : ' + plugin);
-            this.cleanUpAndExitWithCode(1);
-        }
-    },
-    installPlugins: function() {
-        for(var n = 0; n < this.plugins.length; n++) {
-            var plugin = this.plugins[n];
-            this.installSinglePlugin(plugin);
-            if(!this.justBuild) {
-                this.installSinglePlugin(path.join(plugin,"tests"));
-            }
-        }
-
-        if(!this.justBuild) {
-            this.logMessage("cordova-paramedic: installing plugin-test-framework");
-            var plugAddCmd = shell.exec('cordova plugin add https://github.com/apache/cordova-plugin-test-framework',
-                                         {silent:!this.verbose});
-            if(plugAddCmd.code !== 0) {
-                this.logMessage('cordova-plugin-test-framework');
-                this.cleanUpAndExitWithCode(1);
-            }
-        }
-    },
-    cleanUpAndExitWithCode: function(exitCode,resultsObj) {
-        shell.cd(this.storedCWD);
-        // the TMP_FOLDER.removeCallback() call is throwing an exception, so we explicitly delete it here
-        shell.exec('rm -rf ' + this.tempFolder.name);
-        var logStr = this.logOutput ? this.logOutput.join("\n") : null;
-        this.callback(exitCode,resultsObj,logStr);
-    },
-    writeMedicLogUrl: function(url) {
-        this.logMessage("cordova-paramedic: writing medic log url to project");
-        var obj = {logurl:url};
-        fs.writeFileSync(path.join("www","medic.json"),JSON.stringify(obj));
-    },
-    setConfigStartPage: function() {
-        this.logMessage("cordova-paramedic: setting app start page to test page");
-        var fileName = 'config.xml';
-        var configStr = fs.readFileSync(fileName).toString();
-        if(configStr) {
-            configStr = configStr.replace("src=\"index.html\"","src=\"cdvtests/index.html\"");
-            fs.writeFileSync(fileName, configStr);
-        }
-        else {
-            this.logMessage("Oops, could not find config.xml");
-        }
-    },
-    startServer: function() {
-
-        if(this.justBuild) {
-            this.addAndRunPlatform();
-            return;
-        }
-        /// else ....
-
-        this.logMessage("cordova-paramedic: starting local medic server " + this.platformId);
-        var self = this;
-        var server = http.createServer(this.requestListener.bind(this));
-        
-        server.listen(this.port, '127.0.0.1',function onServerConnect() {
-
-            switch(self.platformId) {
-
-                case "android" :
-                    self.writeMedicLogUrl("http://10.0.2.2:" + self.port);
-                    self.addAndRunPlatform();
-                    break;
-                case "wp8" :
-                    //localtunnel(PORT, tunnelCallback);
-                    request.get('http://google.com/', function(e, res, data) {
-                        if(e) {
-                            self.logMessage("failed to detect ip address");
-                            self.cleanUpAndExitWithCode(1);
-                        }
-                        else {
-                            var ip = res.req.connection.localAddress ||
-                                     res.req.socket.localAddress;
-                            self.logMessage("Using ip : " + ip);
-                            self.writeMedicLogUrl("http://" + ip + ":" + self.port);
-                            self.addAndRunPlatform();
-                        }
-                    });
-                    break;
-                case "ios"     :  // intentional fallthrough
-                case "browser" :
-                case "windows" :
-                default :
-                    self.writeMedicLogUrl("http://127.0.0.1:" + self.port);
-                    self.addAndRunPlatform();
-                    break;
-            }
-        });
-    },
-    requestListener: function(request, response) {
-        var self = this;
-        if (request.method == 'PUT' || request.method == 'POST') {
-            var body = '';
-            request.on('data', function (data) {
-                body += data;
-                // Too much POST data, kill the connection!
-                if (body.length > 1e6) {
-                    req.connection.destroy();
-                }
-            });
-            request.on('end', function (res) {
-                if(body.indexOf("mobilespec")  == 2){ // {\"mobilespec\":{...}}
-                    try {
-                        //logMessage("body = " + body);
-                        var results = JSON.parse(body);
-                        self.logMessage("Results: ran " + 
-                                        results.mobilespec.specs + 
-                                        " specs with " + 
-                                        results.mobilespec.failures + 
-                                        " failures");
-                        if(results.mobilespec.failures > 0) {
-                            self.cleanUpAndExitWithCode(1,results);
-                        }
-                        else {
-                            self.cleanUpAndExitWithCode(0,results);
-                        }
-                        
-                    }
-                    catch(err) {
-                        self.logMessage("parse error :: " + err);
-                        self.cleanUpAndExitWithCode(1);
-                    }
-                }
-                else {
-                    self.logMessage("console-log:" + body);
-                }
-            });
-        }
-        else {
-            self.logMessage(request.method);
-            response.writeHead(200, { 'Content-Type': 'text/plain'});
-            response.write("Hello"); // sanity check to make sure server is running
-            response.end();
-        }
-    },
-    addAndRunPlatform: function() {
-        var self = this;
-
-        var plat = this.platformPath || this.platformId;
-        this.logMessage("cordova-paramedic: adding platform : " + plat);
-
-        shell.exec('cordova platform add ' + plat,{silent:!this.verbose});
-        shell.exec('cordova prepare '+ this.browserify,{silent:!this.verbose});   
-
-        if(this.justBuild) {
-
-            this.logMessage("building ...");
-            
-            shell.exec('cordova build ' + this.platformId.split("@")[0],
-                {async:true,silent:!this.verbose},
-                function(code,output){
-                    if(code !== 0) {
-                        self.logMessage("Error: cordova build returned error code " + code);
-                        self.logMessage("output: " + output);
-                        self.cleanUpAndExitWithCode(1);
-                    }
-                    else {
-                        self.cleanUpAndExitWithCode(0);
-                    }
-                }
-            );
-        }
-        else {
-            this.setConfigStartPage();
-
-            shell.exec('cordova emulate ' + this.platformId.split("@")[0] + " --phone",
-                {async:true,silent:!this.verbose},
-                function(code,output){
-                    if(code !== 0) {
-                        self.logMessage("Error: cordova emulate return error code " + code);
-                        self.logMessage("output: " + output);
-                        self.cleanUpAndExitWithCode(1);
-                    }
-                }
-            );
-        }
-    },
-    tunnelCallback: function(err, tunnel) {
-        if (err){
-            this.logMessage("failed to create tunnel url, check your internet connectivity.");
-            this.cleanUpAndExitWithCode(1);
-        }
-        else {
-            // the assigned public url for your tunnel
-            // i.e. https://abcdefgjhij.localtunnel.me
-            this.tunneledUrl = tunnel.url;
-            this.logMessage("cordova-paramedic: tunneledURL = " + tunneledUrl);
-            this.writeMedicLogUrl(tunneledUrl);
-            this.addAndRunPlatform();
-        }
-    }
-};
-
-var storedCWD =  null;
-
-exports.run = function(_platformId,_plugins,_callback,bJustBuild,nPort,msTimeout,bBrowserify,bSilent,bVerbose,platformPath) {
-
-    storedCWD = storedCWD || process.cwd();
-    if(!_plugins) {
-        _plugins = process.cwd();
-    }
-    if(_platformId && _plugins) {
-
-        // make it an array if it's not
-        var plugins = Array.isArray(_plugins) ? _plugins : [_plugins];
-
-        // if we are passed a callback, we will use it, 
-        // otherwise just make a quick and dirty one
-        var callback = ( _callback && _callback.apply ) ? _callback : function(resCode,resObj) {
-            process.exit(resCode);
-        };
-
-        var runner = new ParamedicRunner(_platformId, plugins, callback, !!bJustBuild,
-                                         nPort || PORT, msTimeout || TIMEOUT, !!bBrowserify, !!bSilent, !!bVerbose, platformPath);
-
-        runner.storedCWD = storedCWD;
-        return runner.run();
-    }
-    else {
-        console.error("Error : Missing platformId and/or plugins");
-    }
-};

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/sample-config/.paramedic-core-plugins.config.js
----------------------------------------------------------------------
diff --git a/sample-config/.paramedic-core-plugins.config.js b/sample-config/.paramedic-core-plugins.config.js
new file mode 100644
index 0000000..68699c0
--- /dev/null
+++ b/sample-config/.paramedic-core-plugins.config.js
@@ -0,0 +1,51 @@
+module.exports = {
+    //"externalServerUrl": "http://10.0.8.254" ,
+    "useTunnel": true,
+    "plugins": [
+        "https://github.com/apache/cordova-plugin-battery-status",
+        "https://github.com/apache/cordova-plugin-camera",
+        "https://github.com/apache/cordova-plugin-console",
+        "https://github.com/apache/cordova-plugin-contacts",
+        "https://github.com/apache/cordova-plugin-device",
+        "https://github.com/apache/cordova-plugin-device-motion",
+        "https://github.com/apache/cordova-plugin-device-orientation",
+        "https://github.com/apache/cordova-plugin-dialogs",
+        "https://github.com/apache/cordova-plugin-file",
+        "https://github.com/apache/cordova-plugin-file-transfer",
+        "https://github.com/apache/cordova-plugin-geolocation",
+        "https://github.com/apache/cordova-plugin-globalization",
+        "https://github.com/apache/cordova-plugin-inappbrowser",
+        "https://github.com/apache/cordova-plugin-media",
+        "https://github.com/apache/cordova-plugin-media-capture",
+        "https://github.com/apache/cordova-plugin-network-information",
+        "https://github.com/apache/cordova-plugin-splashscreen",
+        "https://github.com/apache/cordova-plugin-statusbar",
+        "https://github.com/apache/cordova-plugin-vibration"
+    ],
+    "targets": [
+        {
+            "platform": "ios@https://github.com/apache/cordova-ios.git",
+            "action": "run",
+             "args": "--device"
+        },
+        {
+            "platform": "android@https://github.com/apache/cordova-android.git",
+            "action": "run",
+             "args": "--device"
+        },
+        {    // Windows 8.1 Desktop(anycpu)
+            "platform": "windows@https://github.com/apache/cordova-windows.git",
+            "action": "run"
+        },
+        {   // Windows 10 Desktop(x64)
+            "platform": "windows@https://github.com/apache/cordova-windows.git",
+            "action": "run",
+            "args": "--archs=x64 -- --appx=uap"
+        },
+        {    // WP 8.1 Device(arm)
+            "platform": "windows@https://github.com/apache/cordova-windows.git",
+            "action": "run",
+            "args": "--archs=arm --device -- --phone"
+        }
+    ]
+}

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/sample-config/.paramedic.config.js
----------------------------------------------------------------------
diff --git a/sample-config/.paramedic.config.js b/sample-config/.paramedic.config.js
new file mode 100644
index 0000000..77d77ff
--- /dev/null
+++ b/sample-config/.paramedic.config.js
@@ -0,0 +1,33 @@
+module.exports = {
+    //"externalServerUrl": "http://10.0.8.254",
+    "useTunnel": true,
+    "plugins": [
+        "https://github.com/apache/cordova-plugin-inappbrowser"
+    ],
+    "targets": [
+        {
+            "platform": "ios@https://github.com/apache/cordova-ios.git",
+            "action": "run",
+             "args": "--device"
+        },
+        {
+            "platform": "android@https://github.com/apache/cordova-android.git",
+            "action": "run",
+             "args": "--device"
+        },
+        {    // Windows 8.1 Desktop(anycpu)
+            "platform": "windows",
+            "action": "run"
+        },
+        {   // Windows 10 Desktop(x64)
+            "platform": "windows@https://github.com/apache/cordova-windows.git",
+            "action": "run",
+            "args": "--archs=x64 -- --appx=uap"
+        },
+        // {    // WP 8.1 Device(arm)
+        //     "platform": "windows@https://github.com/apache/cordova-windows.git",
+        //     "action": "run",
+        //     "args": "--archs=arm --device -- --phone"
+        // }
+    ]
+};


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[2/3] cordova-paramedic git commit: Adding real time logging and other improvements

Posted by sg...@apache.org.
http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/paramedic-plugin/socket.io.js
----------------------------------------------------------------------
diff --git a/paramedic-plugin/socket.io.js b/paramedic-plugin/socket.io.js
new file mode 100644
index 0000000..6f478d8
--- /dev/null
+++ b/paramedic-plugin/socket.io.js
@@ -0,0 +1,4 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.io=f()}})(function(){var define,module,exports;return 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(_dereq_,module,exports){module.exports=_dereq_("./lib/")},{"./lib/":2}],2:[function(_dereq_,module,exports){module.exports=_dereq_("./socket");module.exports.parser=_dereq_("engine.io
 -parser")},{"./socket":3,"engine.io-parser":19}],3:[function(_dereq_,module,exports){(function(global){var transports=_dereq_("./transports");var Emitter=_dereq_("component-emitter");var debug=_dereq_("debug")("engine.io-client:socket");var index=_dereq_("indexof");var parser=_dereq_("engine.io-parser");var parseuri=_dereq_("parseuri");var parsejson=_dereq_("parsejson");var parseqs=_dereq_("parseqs");module.exports=Socket;function noop(){}function Socket(uri,opts){if(!(this instanceof Socket))return new Socket(uri,opts);opts=opts||{};if(uri&&"object"==typeof uri){opts=uri;uri=null}if(uri){uri=parseuri(uri);opts.hostname=uri.host;opts.secure=uri.protocol=="https"||uri.protocol=="wss";opts.port=uri.port;if(uri.query)opts.query=uri.query}else if(opts.host){opts.hostname=parseuri(opts.host).host}this.secure=null!=opts.secure?opts.secure:global.location&&"https:"==location.protocol;if(opts.hostname&&!opts.port){opts.port=this.secure?"443":"80"}this.agent=opts.agent||false;this.hostname=o
 pts.hostname||(global.location?location.hostname:"localhost");this.port=opts.port||(global.location&&location.port?location.port:this.secure?443:80);this.query=opts.query||{};if("string"==typeof this.query)this.query=parseqs.decode(this.query);this.upgrade=false!==opts.upgrade;this.path=(opts.path||"/engine.io").replace(/\/$/,"")+"/";this.forceJSONP=!!opts.forceJSONP;this.jsonp=false!==opts.jsonp;this.forceBase64=!!opts.forceBase64;this.enablesXDR=!!opts.enablesXDR;this.timestampParam=opts.timestampParam||"t";this.timestampRequests=opts.timestampRequests;this.transports=opts.transports||["polling","websocket"];this.readyState="";this.writeBuffer=[];this.policyPort=opts.policyPort||843;this.rememberUpgrade=opts.rememberUpgrade||false;this.binaryType=null;this.onlyBinaryUpgrades=opts.onlyBinaryUpgrades;this.perMessageDeflate=false!==opts.perMessageDeflate?opts.perMessageDeflate||{}:false;if(true===this.perMessageDeflate)this.perMessageDeflate={};if(this.perMessageDeflate&&null==this.p
 erMessageDeflate.threshold){this.perMessageDeflate.threshold=1024}this.pfx=opts.pfx||null;this.key=opts.key||null;this.passphrase=opts.passphrase||null;this.cert=opts.cert||null;this.ca=opts.ca||null;this.ciphers=opts.ciphers||null;this.rejectUnauthorized=opts.rejectUnauthorized===undefined?null:opts.rejectUnauthorized;var freeGlobal=typeof global=="object"&&global;if(freeGlobal.global===freeGlobal){if(opts.extraHeaders&&Object.keys(opts.extraHeaders).length>0){this.extraHeaders=opts.extraHeaders}}this.open()}Socket.priorWebsocketSuccess=false;Emitter(Socket.prototype);Socket.protocol=parser.protocol;Socket.Socket=Socket;Socket.Transport=_dereq_("./transport");Socket.transports=_dereq_("./transports");Socket.parser=_dereq_("engine.io-parser");Socket.prototype.createTransport=function(name){debug('creating transport "%s"',name);var query=clone(this.query);query.EIO=parser.protocol;query.transport=name;if(this.id)query.sid=this.id;var transport=new transports[name]({agent:this.agent,h
 ostname:this.hostname,port:this.port,secure:this.secure,path:this.path,query:query,forceJSONP:this.forceJSONP,jsonp:this.jsonp,forceBase64:this.forceBase64,enablesXDR:this.enablesXDR,timestampRequests:this.timestampRequests,timestampParam:this.timestampParam,policyPort:this.policyPort,socket:this,pfx:this.pfx,key:this.key,passphrase:this.passphrase,cert:this.cert,ca:this.ca,ciphers:this.ciphers,rejectUnauthorized:this.rejectUnauthorized,perMessageDeflate:this.perMessageDeflate,extraHeaders:this.extraHeaders});return transport};function clone(obj){var o={};for(var i in obj){if(obj.hasOwnProperty(i)){o[i]=obj[i]}}return o}Socket.prototype.open=function(){var transport;if(this.rememberUpgrade&&Socket.priorWebsocketSuccess&&this.transports.indexOf("websocket")!=-1){transport="websocket"}else if(0===this.transports.length){var self=this;setTimeout(function(){self.emit("error","No transports available")},0);return}else{transport=this.transports[0]}this.readyState="opening";try{transport=t
 his.createTransport(transport)}catch(e){this.transports.shift();this.open();return}transport.open();this.setTransport(transport)};Socket.prototype.setTransport=function(transport){debug("setting transport %s",transport.name);var self=this;if(this.transport){debug("clearing existing transport %s",this.transport.name);this.transport.removeAllListeners()}this.transport=transport;transport.on("drain",function(){self.onDrain()}).on("packet",function(packet){self.onPacket(packet)}).on("error",function(e){self.onError(e)}).on("close",function(){self.onClose("transport close")})};Socket.prototype.probe=function(name){debug('probing transport "%s"',name);var transport=this.createTransport(name,{probe:1}),failed=false,self=this;Socket.priorWebsocketSuccess=false;function onTransportOpen(){if(self.onlyBinaryUpgrades){var upgradeLosesBinary=!this.supportsBinary&&self.transport.supportsBinary;failed=failed||upgradeLosesBinary}if(failed)return;debug('probe transport "%s" opened',name);transport.s
 end([{type:"ping",data:"probe"}]);transport.once("packet",function(msg){if(failed)return;if("pong"==msg.type&&"probe"==msg.data){debug('probe transport "%s" pong',name);self.upgrading=true;self.emit("upgrading",transport);if(!transport)return;Socket.priorWebsocketSuccess="websocket"==transport.name;debug('pausing current transport "%s"',self.transport.name);self.transport.pause(function(){if(failed)return;if("closed"==self.readyState)return;debug("changing transport and sending upgrade packet");cleanup();self.setTransport(transport);transport.send([{type:"upgrade"}]);self.emit("upgrade",transport);transport=null;self.upgrading=false;self.flush()})}else{debug('probe transport "%s" failed',name);var err=new Error("probe error");err.transport=transport.name;self.emit("upgradeError",err)}})}function freezeTransport(){if(failed)return;failed=true;cleanup();transport.close();transport=null}function onerror(err){var error=new Error("probe error: "+err);error.transport=transport.name;freeze
 Transport();debug('probe transport "%s" failed because of error: %s',name,err);self.emit("upgradeError",error)}function onTransportClose(){onerror("transport closed")}function onclose(){onerror("socket closed")}function onupgrade(to){if(transport&&to.name!=transport.name){debug('"%s" works - aborting "%s"',to.name,transport.name);freezeTransport()}}function cleanup(){transport.removeListener("open",onTransportOpen);transport.removeListener("error",onerror);transport.removeListener("close",onTransportClose);self.removeListener("close",onclose);self.removeListener("upgrading",onupgrade)}transport.once("open",onTransportOpen);transport.once("error",onerror);transport.once("close",onTransportClose);this.once("close",onclose);this.once("upgrading",onupgrade);transport.open()};Socket.prototype.onOpen=function(){debug("socket open");this.readyState="open";Socket.priorWebsocketSuccess="websocket"==this.transport.name;this.emit("open");this.flush();if("open"==this.readyState&&this.upgrade&&t
 his.transport.pause){debug("starting upgrade probes");for(var i=0,l=this.upgrades.length;i<l;i++){this.probe(this.upgrades[i])}}};Socket.prototype.onPacket=function(packet){if("opening"==this.readyState||"open"==this.readyState){debug('socket receive: type "%s", data "%s"',packet.type,packet.data);this.emit("packet",packet);this.emit("heartbeat");switch(packet.type){case"open":this.onHandshake(parsejson(packet.data));break;case"pong":this.setPing();this.emit("pong");break;case"error":var err=new Error("server error");err.code=packet.data;this.onError(err);break;case"message":this.emit("data",packet.data);this.emit("message",packet.data);break}}else{debug('packet received with socket readyState "%s"',this.readyState)}};Socket.prototype.onHandshake=function(data){this.emit("handshake",data);this.id=data.sid;this.transport.query.sid=data.sid;this.upgrades=this.filterUpgrades(data.upgrades);this.pingInterval=data.pingInterval;this.pingTimeout=data.pingTimeout;this.onOpen();if("closed"==
 this.readyState)return;this.setPing();this.removeListener("heartbeat",this.onHeartbeat);this.on("heartbeat",this.onHeartbeat)};Socket.prototype.onHeartbeat=function(timeout){clearTimeout(this.pingTimeoutTimer);var self=this;self.pingTimeoutTimer=setTimeout(function(){if("closed"==self.readyState)return;self.onClose("ping timeout")},timeout||self.pingInterval+self.pingTimeout)};Socket.prototype.setPing=function(){var self=this;clearTimeout(self.pingIntervalTimer);self.pingIntervalTimer=setTimeout(function(){debug("writing ping packet - expecting pong within %sms",self.pingTimeout);self.ping();self.onHeartbeat(self.pingTimeout)},self.pingInterval)};Socket.prototype.ping=function(){var self=this;this.sendPacket("ping",function(){self.emit("ping")})};Socket.prototype.onDrain=function(){this.writeBuffer.splice(0,this.prevBufferLen);this.prevBufferLen=0;if(0===this.writeBuffer.length){this.emit("drain")}else{this.flush()}};Socket.prototype.flush=function(){if("closed"!=this.readyState&&th
 is.transport.writable&&!this.upgrading&&this.writeBuffer.length){debug("flushing %d packets in socket",this.writeBuffer.length);this.transport.send(this.writeBuffer);this.prevBufferLen=this.writeBuffer.length;this.emit("flush")}};Socket.prototype.write=Socket.prototype.send=function(msg,options,fn){this.sendPacket("message",msg,options,fn);return this};Socket.prototype.sendPacket=function(type,data,options,fn){if("function"==typeof data){fn=data;data=undefined}if("function"==typeof options){fn=options;options=null}if("closing"==this.readyState||"closed"==this.readyState){return}options=options||{};options.compress=false!==options.compress;var packet={type:type,data:data,options:options};this.emit("packetCreate",packet);this.writeBuffer.push(packet);if(fn)this.once("flush",fn);this.flush()};Socket.prototype.close=function(){if("opening"==this.readyState||"open"==this.readyState){this.readyState="closing";var self=this;if(this.writeBuffer.length){this.once("drain",function(){if(this.u
 pgrading){waitForUpgrade()}else{close()}})}else if(this.upgrading){waitForUpgrade()}else{close()}}function close(){self.onClose("forced close");debug("socket closing - telling transport to close");self.transport.close()}function cleanupAndClose(){self.removeListener("upgrade",cleanupAndClose);self.removeListener("upgradeError",cleanupAndClose);close()}function waitForUpgrade(){self.once("upgrade",cleanupAndClose);self.once("upgradeError",cleanupAndClose)}return this};Socket.prototype.onError=function(err){debug("socket error %j",err);Socket.priorWebsocketSuccess=false;this.emit("error",err);this.onClose("transport error",err)};Socket.prototype.onClose=function(reason,desc){if("opening"==this.readyState||"open"==this.readyState||"closing"==this.readyState){debug('socket close with reason: "%s"',reason);var self=this;clearTimeout(this.pingIntervalTimer);clearTimeout(this.pingTimeoutTimer);this.transport.removeAllListeners("close");this.transport.close();this.transport.removeAllListene
 rs();this.readyState="closed";this.id=null;this.emit("close",reason,desc);self.writeBuffer=[];self.prevBufferLen=0}};Socket.prototype.filterUpgrades=function(upgrades){var filteredUpgrades=[];for(var i=0,j=upgrades.length;i<j;i++){if(~index(this.transports,upgrades[i]))filteredUpgrades.push(upgrades[i])}return filteredUpgrades}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./transport":4,"./transports":5,"component-emitter":15,debug:17,"engine.io-parser":19,indexof:23,parsejson:26,parseqs:27,parseuri:28}],4:[function(_dereq_,module,exports){var parser=_dereq_("engine.io-parser");var Emitter=_dereq_("component-emitter");module.exports=Transport;function Transport(opts){this.path=opts.path;this.hostname=opts.hostname;this.port=opts.port;this.secure=opts.secure;this.query=opts.query;this.timestampParam=opts.timestampParam;this.timestampRequests=opts.timestampRequests;this.readyState="";this.agent=opts.agent||fals
 e;this.socket=opts.socket;this.enablesXDR=opts.enablesXDR;this.pfx=opts.pfx;this.key=opts.key;this.passphrase=opts.passphrase;this.cert=opts.cert;this.ca=opts.ca;this.ciphers=opts.ciphers;this.rejectUnauthorized=opts.rejectUnauthorized;this.extraHeaders=opts.extraHeaders}Emitter(Transport.prototype);Transport.prototype.onError=function(msg,desc){var err=new Error(msg);err.type="TransportError";err.description=desc;this.emit("error",err);return this};Transport.prototype.open=function(){if("closed"==this.readyState||""==this.readyState){this.readyState="opening";this.doOpen()}return this};Transport.prototype.close=function(){if("opening"==this.readyState||"open"==this.readyState){this.doClose();this.onClose()}return this};Transport.prototype.send=function(packets){if("open"==this.readyState){this.write(packets)}else{throw new Error("Transport not open")}};Transport.prototype.onOpen=function(){this.readyState="open";this.writable=true;this.emit("open")};Transport.prototype.onData=funct
 ion(data){var packet=parser.decodePacket(data,this.socket.binaryType);this.onPacket(packet)};Transport.prototype.onPacket=function(packet){this.emit("packet",packet)};Transport.prototype.onClose=function(){this.readyState="closed";this.emit("close")}},{"component-emitter":15,"engine.io-parser":19}],5:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_("xmlhttprequest-ssl");var XHR=_dereq_("./polling-xhr");var JSONP=_dereq_("./polling-jsonp");var websocket=_dereq_("./websocket");exports.polling=polling;exports.websocket=websocket;function polling(opts){var xhr;var xd=false;var xs=false;var jsonp=false!==opts.jsonp;if(global.location){var isSSL="https:"==location.protocol;var port=location.port;if(!port){port=isSSL?443:80}xd=opts.hostname!=location.hostname||port!=opts.port;xs=opts.secure!=isSSL}opts.xdomain=xd;opts.xscheme=xs;xhr=new XMLHttpRequest(opts);if("open"in xhr&&!opts.forceJSONP){return new XHR(opts)}else{if(!jsonp)throw new Error("JSONP disabled")
 ;return new JSONP(opts)}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./polling-jsonp":6,"./polling-xhr":7,"./websocket":9,"xmlhttprequest-ssl":10}],6:[function(_dereq_,module,exports){(function(global){var Polling=_dereq_("./polling");var inherit=_dereq_("component-inherit");module.exports=JSONPPolling;var rNewline=/\n/g;var rEscapedNewline=/\\n/g;var callbacks;var index=0;function empty(){}function JSONPPolling(opts){Polling.call(this,opts);this.query=this.query||{};if(!callbacks){if(!global.___eio)global.___eio=[];callbacks=global.___eio}this.index=callbacks.length;var self=this;callbacks.push(function(msg){self.onData(msg)});this.query.j=this.index;if(global.document&&global.addEventListener){global.addEventListener("beforeunload",function(){if(self.script)self.script.onerror=empty},false)}}inherit(JSONPPolling,Polling);JSONPPolling.prototype.supportsBinary=false;JSONPPolling.prototype.doClose=function()
 {if(this.script){this.script.parentNode.removeChild(this.script);this.script=null}if(this.form){this.form.parentNode.removeChild(this.form);this.form=null;this.iframe=null}Polling.prototype.doClose.call(this)};JSONPPolling.prototype.doPoll=function(){var self=this;var script=document.createElement("script");if(this.script){this.script.parentNode.removeChild(this.script);this.script=null}script.async=true;script.src=this.uri();script.onerror=function(e){self.onError("jsonp poll error",e)};var insertAt=document.getElementsByTagName("script")[0];if(insertAt){insertAt.parentNode.insertBefore(script,insertAt)}else{(document.head||document.body).appendChild(script)}this.script=script;var isUAgecko="undefined"!=typeof navigator&&/gecko/i.test(navigator.userAgent);if(isUAgecko){setTimeout(function(){var iframe=document.createElement("iframe");document.body.appendChild(iframe);document.body.removeChild(iframe)},100)}};JSONPPolling.prototype.doWrite=function(data,fn){var self=this;if(!this.fo
 rm){var form=document.createElement("form");var area=document.createElement("textarea");var id=this.iframeId="eio_iframe_"+this.index;var iframe;form.className="socketio";form.style.position="absolute";form.style.top="-1000px";form.style.left="-1000px";form.target=id;form.method="POST";form.setAttribute("accept-charset","utf-8");area.name="d";form.appendChild(area);document.body.appendChild(form);this.form=form;this.area=area}this.form.action=this.uri();function complete(){initIframe();fn()}function initIframe(){if(self.iframe){try{self.form.removeChild(self.iframe)}catch(e){self.onError("jsonp polling iframe removal error",e)}}try{var html='<iframe src="javascript:0" name="'+self.iframeId+'">';iframe=document.createElement(html)}catch(e){iframe=document.createElement("iframe");iframe.name=self.iframeId;iframe.src="javascript:0"}iframe.id=self.iframeId;self.form.appendChild(iframe);self.iframe=iframe}initIframe();data=data.replace(rEscapedNewline,"\\\n");this.area.value=data.replace
 (rNewline,"\\n");try{this.form.submit()}catch(e){}if(this.iframe.attachEvent){this.iframe.onreadystatechange=function(){if(self.iframe.readyState=="complete"){complete()}}}else{this.iframe.onload=complete}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./polling":8,"component-inherit":16}],7:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_("xmlhttprequest-ssl");var Polling=_dereq_("./polling");var Emitter=_dereq_("component-emitter");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling-xhr");module.exports=XHR;module.exports.Request=Request;function empty(){}function XHR(opts){Polling.call(this,opts);if(global.location){var isSSL="https:"==location.protocol;var port=location.port;if(!port){port=isSSL?443:80}this.xd=opts.hostname!=global.location.hostname||port!=opts.port;this.xs=opts.secure!=isSSL}else{this.extraHeaders=opts.extraHeaders}}i
 nherit(XHR,Polling);XHR.prototype.supportsBinary=true;XHR.prototype.request=function(opts){opts=opts||{};opts.uri=this.uri();opts.xd=this.xd;opts.xs=this.xs;opts.agent=this.agent||false;opts.supportsBinary=this.supportsBinary;opts.enablesXDR=this.enablesXDR;opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;opts.extraHeaders=this.extraHeaders;return new Request(opts)};XHR.prototype.doWrite=function(data,fn){var isBinary=typeof data!=="string"&&data!==undefined;var req=this.request({method:"POST",data:data,isBinary:isBinary});var self=this;req.on("success",fn);req.on("error",function(err){self.onError("xhr post error",err)});this.sendXhr=req};XHR.prototype.doPoll=function(){debug("xhr poll");var req=this.request();var self=this;req.on("data",function(data){self.onData(data)});req.on("error",function(err){self.onError("xhr poll error",err)});this.pollXhr=req};
 function Request(opts){this.method=opts.method||"GET";this.uri=opts.uri;this.xd=!!opts.xd;this.xs=!!opts.xs;this.async=false!==opts.async;this.data=undefined!=opts.data?opts.data:null;this.agent=opts.agent;this.isBinary=opts.isBinary;this.supportsBinary=opts.supportsBinary;this.enablesXDR=opts.enablesXDR;this.pfx=opts.pfx;this.key=opts.key;this.passphrase=opts.passphrase;this.cert=opts.cert;this.ca=opts.ca;this.ciphers=opts.ciphers;this.rejectUnauthorized=opts.rejectUnauthorized;this.extraHeaders=opts.extraHeaders;this.create()}Emitter(Request.prototype);Request.prototype.create=function(){var opts={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;var xhr=this.xhr=new XMLHttpRequest(opts);var self=this;try{debug("xhr open %s: %s",this.method,this.uri);xhr.open(this.method,this.uri
 ,this.async);try{if(this.extraHeaders){xhr.setDisableHeaderCheck(true);for(var i in this.extraHeaders){if(this.extraHeaders.hasOwnProperty(i)){xhr.setRequestHeader(i,this.extraHeaders[i])}}}}catch(e){}if(this.supportsBinary){xhr.responseType="arraybuffer"}if("POST"==this.method){try{if(this.isBinary){xhr.setRequestHeader("Content-type","application/octet-stream")}else{xhr.setRequestHeader("Content-type","text/plain;charset=UTF-8")}}catch(e){}}if("withCredentials"in xhr){xhr.withCredentials=true}if(this.hasXDR()){xhr.onload=function(){self.onLoad()};xhr.onerror=function(){self.onError(xhr.responseText)}}else{xhr.onreadystatechange=function(){if(4!=xhr.readyState)return;if(200==xhr.status||1223==xhr.status){self.onLoad()}else{setTimeout(function(){self.onError(xhr.status)},0)}}}debug("xhr data %s",this.data);xhr.send(this.data)}catch(e){setTimeout(function(){self.onError(e)},0);return}if(global.document){this.index=Request.requestsCount++;Request.requests[this.index]=this}};Request.pr
 ototype.onSuccess=function(){this.emit("success");this.cleanup()};Request.prototype.onData=function(data){this.emit("data",data);this.onSuccess()};Request.prototype.onError=function(err){this.emit("error",err);this.cleanup(true)};Request.prototype.cleanup=function(fromError){if("undefined"==typeof this.xhr||null===this.xhr){return}if(this.hasXDR()){this.xhr.onload=this.xhr.onerror=empty}else{this.xhr.onreadystatechange=empty}if(fromError){try{this.xhr.abort()}catch(e){}}if(global.document){delete Request.requests[this.index]}this.xhr=null};Request.prototype.onLoad=function(){var data;try{var contentType;try{contentType=this.xhr.getResponseHeader("Content-Type").split(";")[0]}catch(e){}if(contentType==="application/octet-stream"){data=this.xhr.response}else{if(!this.supportsBinary){data=this.xhr.responseText}else{try{data=String.fromCharCode.apply(null,new Uint8Array(this.xhr.response))}catch(e){var ui8Arr=new Uint8Array(this.xhr.response);var dataArray=[];for(var idx=0,length=ui8Arr
 .length;idx<length;idx++){dataArray.push(ui8Arr[idx])}data=String.fromCharCode.apply(null,dataArray)}}}}catch(e){this.onError(e)}if(null!=data){this.onData(data)}};Request.prototype.hasXDR=function(){return"undefined"!==typeof global.XDomainRequest&&!this.xs&&this.enablesXDR};Request.prototype.abort=function(){this.cleanup()};if(global.document){Request.requestsCount=0;Request.requests={};if(global.attachEvent){global.attachEvent("onunload",unloadHandler)}else if(global.addEventListener){global.addEventListener("beforeunload",unloadHandler,false)}}function unloadHandler(){for(var i in Request.requests){if(Request.requests.hasOwnProperty(i)){Request.requests[i].abort()}}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./polling":8,"component-emitter":15,"component-inherit":16,debug:17,"xmlhttprequest-ssl":10}],8:[function(_dereq_,module,exports){var Transport=_dereq_("../transport");var parseqs=_dereq_("parseqs"
 );var parser=_dereq_("engine.io-parser");var inherit=_dereq_("component-inherit");var yeast=_dereq_("yeast");var debug=_dereq_("debug")("engine.io-client:polling");module.exports=Polling;var hasXHR2=function(){var XMLHttpRequest=_dereq_("xmlhttprequest-ssl");var xhr=new XMLHttpRequest({xdomain:false});return null!=xhr.responseType}();function Polling(opts){var forceBase64=opts&&opts.forceBase64;if(!hasXHR2||forceBase64){this.supportsBinary=false}Transport.call(this,opts)}inherit(Polling,Transport);Polling.prototype.name="polling";Polling.prototype.doOpen=function(){this.poll()};Polling.prototype.pause=function(onPause){var pending=0;var self=this;this.readyState="pausing";function pause(){debug("paused");self.readyState="paused";onPause()}if(this.polling||!this.writable){var total=0;if(this.polling){debug("we are currently polling - waiting to pause");total++;this.once("pollComplete",function(){debug("pre-pause polling complete");--total||pause()})}if(!this.writable){debug("we are c
 urrently writing - waiting to pause");total++;this.once("drain",function(){debug("pre-pause writing complete");--total||pause()})}}else{pause()}};Polling.prototype.poll=function(){debug("polling");this.polling=true;this.doPoll();this.emit("poll")};Polling.prototype.onData=function(data){var self=this;debug("polling got data %s",data);var callback=function(packet,index,total){if("opening"==self.readyState){self.onOpen()}if("close"==packet.type){self.onClose();return false}self.onPacket(packet)};parser.decodePayload(data,this.socket.binaryType,callback);if("closed"!=this.readyState){this.polling=false;this.emit("pollComplete");if("open"==this.readyState){this.poll()}else{debug('ignoring poll - transport state "%s"',this.readyState)}}};Polling.prototype.doClose=function(){var self=this;function close(){debug("writing close packet");self.write([{type:"close"}])}if("open"==this.readyState){debug("transport open - closing");close()}else{debug("transport not open - deferring close");this.o
 nce("open",close)}};Polling.prototype.write=function(packets){var self=this;this.writable=false;var callbackfn=function(){self.writable=true;self.emit("drain")};var self=this;parser.encodePayload(packets,this.supportsBinary,function(data){self.doWrite(data,callbackfn)})};Polling.prototype.uri=function(){var query=this.query||{};var schema=this.secure?"https":"http";var port="";if(false!==this.timestampRequests){query[this.timestampParam]=yeast()}if(!this.supportsBinary&&!query.sid){query.b64=1}query=parseqs.encode(query);if(this.port&&("https"==schema&&this.port!=443||"http"==schema&&this.port!=80)){port=":"+this.port}if(query.length){query="?"+query}var ipv6=this.hostname.indexOf(":")!==-1;return schema+"://"+(ipv6?"["+this.hostname+"]":this.hostname)+port+this.path+query}},{"../transport":4,"component-inherit":16,debug:17,"engine.io-parser":19,parseqs:27,"xmlhttprequest-ssl":10,yeast:30}],9:[function(_dereq_,module,exports){(function(global){var Transport=_dereq_("../transport");v
 ar parser=_dereq_("engine.io-parser");var parseqs=_dereq_("parseqs");var inherit=_dereq_("component-inherit");var yeast=_dereq_("yeast");var debug=_dereq_("debug")("engine.io-client:websocket");var BrowserWebSocket=global.WebSocket||global.MozWebSocket;var WebSocket=BrowserWebSocket;if(!WebSocket&&typeof window==="undefined"){try{WebSocket=_dereq_("ws")}catch(e){}}module.exports=WS;function WS(opts){var forceBase64=opts&&opts.forceBase64;if(forceBase64){this.supportsBinary=false}this.perMessageDeflate=opts.perMessageDeflate;Transport.call(this,opts)}inherit(WS,Transport);WS.prototype.name="websocket";WS.prototype.supportsBinary=true;WS.prototype.doOpen=function(){if(!this.check()){return}var self=this;var uri=this.uri();var protocols=void 0;var opts={agent:this.agent,perMessageDeflate:this.perMessageDeflate};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized
 ;if(this.extraHeaders){opts.headers=this.extraHeaders}this.ws=BrowserWebSocket?new WebSocket(uri):new WebSocket(uri,protocols,opts);if(this.ws.binaryType===undefined){this.supportsBinary=false}if(this.ws.supports&&this.ws.supports.binary){this.supportsBinary=true;this.ws.binaryType="buffer"}else{this.ws.binaryType="arraybuffer"}this.addEventListeners()};WS.prototype.addEventListeners=function(){var self=this;this.ws.onopen=function(){self.onOpen()};this.ws.onclose=function(){self.onClose()};this.ws.onmessage=function(ev){self.onData(ev.data)};this.ws.onerror=function(e){self.onError("websocket error",e)}};if("undefined"!=typeof navigator&&/iPad|iPhone|iPod/i.test(navigator.userAgent)){WS.prototype.onData=function(data){var self=this;setTimeout(function(){Transport.prototype.onData.call(self,data)},0)}}WS.prototype.write=function(packets){var self=this;this.writable=false;var total=packets.length;for(var i=0,l=total;i<l;i++){(function(packet){parser.encodePacket(packet,self.supportsB
 inary,function(data){if(!BrowserWebSocket){var opts={};if(packet.options){opts.compress=packet.options.compress}if(self.perMessageDeflate){var len="string"==typeof data?global.Buffer.byteLength(data):data.length;if(len<self.perMessageDeflate.threshold){opts.compress=false}}}try{if(BrowserWebSocket){self.ws.send(data)}else{self.ws.send(data,opts)}}catch(e){debug("websocket closed before onclose event")}--total||done()})})(packets[i])}function done(){self.emit("flush");setTimeout(function(){self.writable=true;self.emit("drain")},0)}};WS.prototype.onClose=function(){Transport.prototype.onClose.call(this)};WS.prototype.doClose=function(){if(typeof this.ws!=="undefined"){this.ws.close()}};WS.prototype.uri=function(){var query=this.query||{};var schema=this.secure?"wss":"ws";var port="";if(this.port&&("wss"==schema&&this.port!=443||"ws"==schema&&this.port!=80)){port=":"+this.port}if(this.timestampRequests){query[this.timestampParam]=yeast()}if(!this.supportsBinary){query.b64=1}query=parse
 qs.encode(query);if(query.length){query="?"+query}var ipv6=this.hostname.indexOf(":")!==-1;return schema+"://"+(ipv6?"["+this.hostname+"]":this.hostname)+port+this.path+query};WS.prototype.check=function(){return!!WebSocket&&!("__initialize"in WebSocket&&this.name===WS.prototype.name)}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"../transport":4,"component-inherit":16,debug:17,"engine.io-parser":19,parseqs:27,ws:undefined,yeast:30}],10:[function(_dereq_,module,exports){var hasCORS=_dereq_("has-cors");module.exports=function(opts){var xdomain=opts.xdomain;var xscheme=opts.xscheme;var enablesXDR=opts.enablesXDR;try{if("undefined"!=typeof XMLHttpRequest&&(!xdomain||hasCORS)){return new XMLHttpRequest}}catch(e){}try{if("undefined"!=typeof XDomainRequest&&!xscheme&&enablesXDR){return new XDomainRequest}}catch(e){}if(!xdomain){try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}}}},{"has-cors":22}],11:[func
 tion(_dereq_,module,exports){module.exports=after;function after(count,callback,err_cb){var bail=false;err_cb=err_cb||noop;proxy.count=count;return count===0?callback():proxy;function proxy(err,result){if(proxy.count<=0){throw new Error("after called too many times")}--proxy.count;if(err){bail=true;callback(err);callback=err_cb}else if(proxy.count===0&&!bail){callback(null,result)}}}function noop(){}},{}],12:[function(_dereq_,module,exports){module.exports=function(arraybuffer,start,end){var bytes=arraybuffer.byteLength;start=start||0;end=end||bytes;if(arraybuffer.slice){return arraybuffer.slice(start,end)}if(start<0){start+=bytes}if(end<0){end+=bytes}if(end>bytes){end=bytes}if(start>=bytes||start>=end||bytes===0){return new ArrayBuffer(0)}var abv=new Uint8Array(arraybuffer);var result=new Uint8Array(end-start);for(var i=start,ii=0;i<end;i++,ii++){result[ii]=abv[i]}return result.buffer}},{}],13:[function(_dereq_,module,exports){(function(chars){"use strict";exports.encode=function(a
 rraybuffer){var bytes=new Uint8Array(arraybuffer),i,len=bytes.length,base64="";for(i=0;i<len;i+=3){base64+=chars[bytes[i]>>2];
+base64+=chars[(bytes[i]&3)<<4|bytes[i+1]>>4];base64+=chars[(bytes[i+1]&15)<<2|bytes[i+2]>>6];base64+=chars[bytes[i+2]&63]}if(len%3===2){base64=base64.substring(0,base64.length-1)+"="}else if(len%3===1){base64=base64.substring(0,base64.length-2)+"=="}return base64};exports.decode=function(base64){var bufferLength=base64.length*.75,len=base64.length,i,p=0,encoded1,encoded2,encoded3,encoded4;if(base64[base64.length-1]==="="){bufferLength--;if(base64[base64.length-2]==="="){bufferLength--}}var arraybuffer=new ArrayBuffer(bufferLength),bytes=new Uint8Array(arraybuffer);for(i=0;i<len;i+=4){encoded1=chars.indexOf(base64[i]);encoded2=chars.indexOf(base64[i+1]);encoded3=chars.indexOf(base64[i+2]);encoded4=chars.indexOf(base64[i+3]);bytes[p++]=encoded1<<2|encoded2>>4;bytes[p++]=(encoded2&15)<<4|encoded3>>2;bytes[p++]=(encoded3&3)<<6|encoded4&63}return arraybuffer}})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")},{}],14:[function(_dereq_,module,exports){(function(global){
 var BlobBuilder=global.BlobBuilder||global.WebKitBlobBuilder||global.MSBlobBuilder||global.MozBlobBuilder;var blobSupported=function(){try{var a=new Blob(["hi"]);return a.size===2}catch(e){return false}}();var blobSupportsArrayBufferView=blobSupported&&function(){try{var b=new Blob([new Uint8Array([1,2])]);return b.size===2}catch(e){return false}}();var blobBuilderSupported=BlobBuilder&&BlobBuilder.prototype.append&&BlobBuilder.prototype.getBlob;function mapArrayBufferViews(ary){for(var i=0;i<ary.length;i++){var chunk=ary[i];if(chunk.buffer instanceof ArrayBuffer){var buf=chunk.buffer;if(chunk.byteLength!==buf.byteLength){var copy=new Uint8Array(chunk.byteLength);copy.set(new Uint8Array(buf,chunk.byteOffset,chunk.byteLength));buf=copy.buffer}ary[i]=buf}}}function BlobBuilderConstructor(ary,options){options=options||{};var bb=new BlobBuilder;mapArrayBufferViews(ary);for(var i=0;i<ary.length;i++){bb.append(ary[i])}return options.type?bb.getBlob(options.type):bb.getBlob()}function Blob
 Constructor(ary,options){mapArrayBufferViews(ary);return new Blob(ary,options||{})}module.exports=function(){if(blobSupported){return blobSupportsArrayBufferView?global.Blob:BlobConstructor}else if(blobBuilderSupported){return BlobBuilderConstructor}else{return undefined}}()}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],15:[function(_dereq_,module,exports){module.exports=Emitter;function Emitter(obj){if(obj)return mixin(obj)}function mixin(obj){for(var key in Emitter.prototype){obj[key]=Emitter.prototype[key]}return obj}Emitter.prototype.on=Emitter.prototype.addEventListener=function(event,fn){this._callbacks=this._callbacks||{};(this._callbacks[event]=this._callbacks[event]||[]).push(fn);return this};Emitter.prototype.once=function(event,fn){var self=this;this._callbacks=this._callbacks||{};function on(){self.off(event,on);fn.apply(this,arguments)}on.fn=fn;this.on(event,on);return this};Emitter.prototype.of
 f=Emitter.prototype.removeListener=Emitter.prototype.removeAllListeners=Emitter.prototype.removeEventListener=function(event,fn){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this}var callbacks=this._callbacks[event];if(!callbacks)return this;if(1==arguments.length){delete this._callbacks[event];return this}var cb;for(var i=0;i<callbacks.length;i++){cb=callbacks[i];if(cb===fn||cb.fn===fn){callbacks.splice(i,1);break}}return this};Emitter.prototype.emit=function(event){this._callbacks=this._callbacks||{};var args=[].slice.call(arguments,1),callbacks=this._callbacks[event];if(callbacks){callbacks=callbacks.slice(0);for(var i=0,len=callbacks.length;i<len;++i){callbacks[i].apply(this,args)}}return this};Emitter.prototype.listeners=function(event){this._callbacks=this._callbacks||{};return this._callbacks[event]||[]};Emitter.prototype.hasListeners=function(event){return!!this.listeners(event).length}},{}],16:[function(_dereq_,module,exports){module
 .exports=function(a,b){var fn=function(){};fn.prototype=b.prototype;a.prototype=new fn;a.prototype.constructor=a}},{}],17:[function(_dereq_,module,exports){exports=module.exports=_dereq_("./debug");exports.log=log;exports.formatArgs=formatArgs;exports.save=save;exports.load=load;exports.useColors=useColors;exports.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:localstorage();exports.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"];function useColors(){return"WebkitAppearance"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31}exports.formatters.j=function(v){return JSON.stringify(v)};function formatArgs(){var args=arguments;var useColors=this.useColors;args[0]=(useColors?"%c":"")+this.namespace+(useColors?" %c":" ")+args[0]+(useColors?"%c ":" ")+"+"+exports.humanize
 (this.diff);if(!useColors)return args;var c="color: "+this.color;args=[args[0],c,"color: inherit"].concat(Array.prototype.slice.call(args,1));var index=0;var lastC=0;args[0].replace(/%[a-z%]/g,function(match){if("%%"===match)return;index++;if("%c"===match){lastC=index}});args.splice(lastC,0,c);return args}function log(){return"object"===typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function save(namespaces){try{if(null==namespaces){exports.storage.removeItem("debug")}else{exports.storage.debug=namespaces}}catch(e){}}function load(){var r;try{r=exports.storage.debug}catch(e){}return r}exports.enable(load());function localstorage(){try{return window.localStorage}catch(e){}}},{"./debug":18}],18:[function(_dereq_,module,exports){exports=module.exports=debug;exports.coerce=coerce;exports.disable=disable;exports.enable=enable;exports.enabled=enabled;exports.humanize=_dereq_("ms");exports.names=[];exports.skips=[];exports.formatters={};var prevCo
 lor=0;var prevTime;function selectColor(){return exports.colors[prevColor++%exports.colors.length]}function debug(namespace){function disabled(){}disabled.enabled=false;function enabled(){var self=enabled;var curr=+new Date;var ms=curr-(prevTime||curr);self.diff=ms;self.prev=prevTime;self.curr=curr;prevTime=curr;if(null==self.useColors)self.useColors=exports.useColors();if(null==self.color&&self.useColors)self.color=selectColor();var args=Array.prototype.slice.call(arguments);args[0]=exports.coerce(args[0]);if("string"!==typeof args[0]){args=["%o"].concat(args)}var index=0;args[0]=args[0].replace(/%([a-z%])/g,function(match,format){if(match==="%%")return match;index++;var formatter=exports.formatters[format];if("function"===typeof formatter){var val=args[index];match=formatter.call(self,val);args.splice(index,1);index--}return match});if("function"===typeof exports.formatArgs){args=exports.formatArgs.apply(self,args)}var logFn=enabled.log||exports.log||console.log.bind(console);logF
 n.apply(self,args)}enabled.enabled=true;var fn=exports.enabled(namespace)?enabled:disabled;fn.namespace=namespace;return fn}function enable(namespaces){exports.save(namespaces);var split=(namespaces||"").split(/[\s,]+/);var len=split.length;for(var i=0;i<len;i++){if(!split[i])continue;namespaces=split[i].replace(/\*/g,".*?");if(namespaces[0]==="-"){exports.skips.push(new RegExp("^"+namespaces.substr(1)+"$"))}else{exports.names.push(new RegExp("^"+namespaces+"$"))}}}function disable(){exports.enable("")}function enabled(name){var i,len;for(i=0,len=exports.skips.length;i<len;i++){if(exports.skips[i].test(name)){return false}}for(i=0,len=exports.names.length;i<len;i++){if(exports.names[i].test(name)){return true}}return false}function coerce(val){if(val instanceof Error)return val.stack||val.message;return val}},{ms:25}],19:[function(_dereq_,module,exports){(function(global){var keys=_dereq_("./keys");var hasBinary=_dereq_("has-binary");var sliceBuffer=_dereq_("arraybuffer.slice");var 
 base64encoder=_dereq_("base64-arraybuffer");var after=_dereq_("after");var utf8=_dereq_("utf8");var isAndroid=navigator.userAgent.match(/Android/i);var isPhantomJS=/PhantomJS/i.test(navigator.userAgent);var dontSendBlobs=isAndroid||isPhantomJS;exports.protocol=3;var packets=exports.packets={open:0,close:1,ping:2,pong:3,message:4,upgrade:5,noop:6};var packetslist=keys(packets);var err={type:"error",data:"parser error"};var Blob=_dereq_("blob");exports.encodePacket=function(packet,supportsBinary,utf8encode,callback){if("function"==typeof supportsBinary){callback=supportsBinary;supportsBinary=false}if("function"==typeof utf8encode){callback=utf8encode;utf8encode=null}var data=packet.data===undefined?undefined:packet.data.buffer||packet.data;if(global.ArrayBuffer&&data instanceof ArrayBuffer){return encodeArrayBuffer(packet,supportsBinary,callback)}else if(Blob&&data instanceof global.Blob){return encodeBlob(packet,supportsBinary,callback)}if(data&&data.base64){return encodeBase64Object
 (packet,callback)}var encoded=packets[packet.type];if(undefined!==packet.data){encoded+=utf8encode?utf8.encode(String(packet.data)):String(packet.data)}return callback(""+encoded)};function encodeBase64Object(packet,callback){var message="b"+exports.packets[packet.type]+packet.data.data;return callback(message)}function encodeArrayBuffer(packet,supportsBinary,callback){if(!supportsBinary){return exports.encodeBase64Packet(packet,callback)}var data=packet.data;var contentArray=new Uint8Array(data);var resultBuffer=new Uint8Array(1+data.byteLength);resultBuffer[0]=packets[packet.type];for(var i=0;i<contentArray.length;i++){resultBuffer[i+1]=contentArray[i]}return callback(resultBuffer.buffer)}function encodeBlobAsArrayBuffer(packet,supportsBinary,callback){if(!supportsBinary){return exports.encodeBase64Packet(packet,callback)}var fr=new FileReader;fr.onload=function(){packet.data=fr.result;exports.encodePacket(packet,supportsBinary,true,callback)};return fr.readAsArrayBuffer(packet.da
 ta)}function encodeBlob(packet,supportsBinary,callback){if(!supportsBinary){return exports.encodeBase64Packet(packet,callback)}if(dontSendBlobs){return encodeBlobAsArrayBuffer(packet,supportsBinary,callback)}var length=new Uint8Array(1);length[0]=packets[packet.type];var blob=new Blob([length.buffer,packet.data]);return callback(blob)}exports.encodeBase64Packet=function(packet,callback){var message="b"+exports.packets[packet.type];if(Blob&&packet.data instanceof global.Blob){var fr=new FileReader;fr.onload=function(){var b64=fr.result.split(",")[1];callback(message+b64)};return fr.readAsDataURL(packet.data)}var b64data;try{b64data=String.fromCharCode.apply(null,new Uint8Array(packet.data))}catch(e){var typed=new Uint8Array(packet.data);var basic=new Array(typed.length);for(var i=0;i<typed.length;i++){basic[i]=typed[i]}b64data=String.fromCharCode.apply(null,basic)}message+=global.btoa(b64data);return callback(message)};exports.decodePacket=function(data,binaryType,utf8decode){if(type
 of data=="string"||data===undefined){if(data.charAt(0)=="b"){return exports.decodeBase64Packet(data.substr(1),binaryType)}if(utf8decode){try{data=utf8.decode(data)}catch(e){return err}}var type=data.charAt(0);if(Number(type)!=type||!packetslist[type]){return err}if(data.length>1){return{type:packetslist[type],data:data.substring(1)}}else{return{type:packetslist[type]}}}var asArray=new Uint8Array(data);var type=asArray[0];var rest=sliceBuffer(data,1);if(Blob&&binaryType==="blob"){rest=new Blob([rest])}return{type:packetslist[type],data:rest}};exports.decodeBase64Packet=function(msg,binaryType){var type=packetslist[msg.charAt(0)];if(!global.ArrayBuffer){return{type:type,data:{base64:true,data:msg.substr(1)}}}var data=base64encoder.decode(msg.substr(1));if(binaryType==="blob"&&Blob){data=new Blob([data])}return{type:type,data:data}};exports.encodePayload=function(packets,supportsBinary,callback){if(typeof supportsBinary=="function"){callback=supportsBinary;supportsBinary=null}var isBin
 ary=hasBinary(packets);if(supportsBinary&&isBinary){if(Blob&&!dontSendBlobs){return exports.encodePayloadAsBlob(packets,callback)}return exports.encodePayloadAsArrayBuffer(packets,callback)}if(!packets.length){return callback("0:")}function setLengthHeader(message){return message.length+":"+message}function encodeOne(packet,doneCallback){exports.encodePacket(packet,!isBinary?false:supportsBinary,true,function(message){doneCallback(null,setLengthHeader(message))})}map(packets,encodeOne,function(err,results){return callback(results.join(""))})};function map(ary,each,done){var result=new Array(ary.length);var next=after(ary.length,done);var eachWithIndex=function(i,el,cb){each(el,function(error,msg){result[i]=msg;cb(error,result)})};for(var i=0;i<ary.length;i++){eachWithIndex(i,ary[i],next)}}exports.decodePayload=function(data,binaryType,callback){if(typeof data!="string"){return exports.decodePayloadAsBinary(data,binaryType,callback)}if(typeof binaryType==="function"){callback=binaryT
 ype;binaryType=null}var packet;if(data==""){return callback(err,0,1)}var length="",n,msg;for(var i=0,l=data.length;i<l;i++){var chr=data.charAt(i);if(":"!=chr){length+=chr}else{if(""==length||length!=(n=Number(length))){return callback(err,0,1)}msg=data.substr(i+1,n);if(length!=msg.length){return callback(err,0,1)}if(msg.length){packet=exports.decodePacket(msg,binaryType,true);if(err.type==packet.type&&err.data==packet.data){return callback(err,0,1)}var ret=callback(packet,i+n,l);if(false===ret)return}i+=n;length=""}}if(length!=""){return callback(err,0,1)}};exports.encodePayloadAsArrayBuffer=function(packets,callback){if(!packets.length){return callback(new ArrayBuffer(0))}function encodeOne(packet,doneCallback){exports.encodePacket(packet,true,true,function(data){return doneCallback(null,data)})}map(packets,encodeOne,function(err,encodedPackets){var totalLength=encodedPackets.reduce(function(acc,p){var len;if(typeof p==="string"){len=p.length}else{len=p.byteLength}return acc+len.t
 oString().length+len+2},0);var resultArray=new Uint8Array(totalLength);var bufferIndex=0;encodedPackets.forEach(function(p){var isString=typeof p==="string";var ab=p;if(isString){var view=new Uint8Array(p.length);for(var i=0;i<p.length;i++){view[i]=p.charCodeAt(i)}ab=view.buffer}if(isString){resultArray[bufferIndex++]=0}else{resultArray[bufferIndex++]=1}var lenStr=ab.byteLength.toString();for(var i=0;i<lenStr.length;i++){resultArray[bufferIndex++]=parseInt(lenStr[i])}resultArray[bufferIndex++]=255;var view=new Uint8Array(ab);for(var i=0;i<view.length;i++){resultArray[bufferIndex++]=view[i]}});return callback(resultArray.buffer)})};exports.encodePayloadAsBlob=function(packets,callback){function encodeOne(packet,doneCallback){exports.encodePacket(packet,true,true,function(encoded){var binaryIdentifier=new Uint8Array(1);binaryIdentifier[0]=1;if(typeof encoded==="string"){var view=new Uint8Array(encoded.length);for(var i=0;i<encoded.length;i++){view[i]=encoded.charCodeAt(i)}encoded=view
 .buffer;binaryIdentifier[0]=0}var len=encoded instanceof ArrayBuffer?encoded.byteLength:encoded.size;var lenStr=len.toString();var lengthAry=new Uint8Array(lenStr.length+1);for(var i=0;i<lenStr.length;i++){lengthAry[i]=parseInt(lenStr[i])}lengthAry[lenStr.length]=255;if(Blob){var blob=new Blob([binaryIdentifier.buffer,lengthAry.buffer,encoded]);doneCallback(null,blob)}})}map(packets,encodeOne,function(err,results){return callback(new Blob(results))})};exports.decodePayloadAsBinary=function(data,binaryType,callback){if(typeof binaryType==="function"){callback=binaryType;binaryType=null}var bufferTail=data;var buffers=[];var numberTooLong=false;while(bufferTail.byteLength>0){var tailArray=new Uint8Array(bufferTail);var isString=tailArray[0]===0;var msgLength="";for(var i=1;;i++){if(tailArray[i]==255)break;if(msgLength.length>310){numberTooLong=true;break}msgLength+=tailArray[i]}if(numberTooLong)return callback(err,0,1);bufferTail=sliceBuffer(bufferTail,2+msgLength.length);msgLength=pa
 rseInt(msgLength);var msg=sliceBuffer(bufferTail,0,msgLength);if(isString){try{msg=String.fromCharCode.apply(null,new Uint8Array(msg))}catch(e){var typed=new Uint8Array(msg);msg="";for(var i=0;i<typed.length;i++){msg+=String.fromCharCode(typed[i])}}}buffers.push(msg);bufferTail=sliceBuffer(bufferTail,msgLength)}var total=buffers.length;buffers.forEach(function(buffer,i){callback(exports.decodePacket(buffer,binaryType,true),i,total)})}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./keys":20,after:11,"arraybuffer.slice":12,"base64-arraybuffer":13,blob:14,"has-binary":21,utf8:29}],20:[function(_dereq_,module,exports){module.exports=Object.keys||function keys(obj){var arr=[];var has=Object.prototype.hasOwnProperty;for(var i in obj){if(has.call(obj,i)){arr.push(i)}}return arr}},{}],21:[function(_dereq_,module,exports){(function(global){var isArray=_dereq_("isarray");module.exports=hasBinary;function hasBinary(data
 ){function _hasBinary(obj){if(!obj)return false;if(global.Buffer&&global.Buffer.isBuffer(obj)||global.ArrayBuffer&&obj instanceof ArrayBuffer||global.Blob&&obj instanceof Blob||global.File&&obj instanceof File){return true}if(isArray(obj)){for(var i=0;i<obj.length;i++){if(_hasBinary(obj[i])){return true}}}else if(obj&&"object"==typeof obj){if(obj.toJSON){obj=obj.toJSON()}for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key)&&_hasBinary(obj[key])){return true}}}return false}return _hasBinary(data)}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{isarray:24}],22:[function(_dereq_,module,exports){try{module.exports=typeof XMLHttpRequest!=="undefined"&&"withCredentials"in new XMLHttpRequest}catch(err){module.exports=false}},{}],23:[function(_dereq_,module,exports){var indexOf=[].indexOf;module.exports=function(arr,obj){if(indexOf)return arr.indexOf(obj);for(var i=0;i<arr.length;++i){if(arr[i]===obj)ret
 urn i}return-1}},{}],24:[function(_dereq_,module,exports){module.exports=Array.isArray||function(arr){return Object.prototype.toString.call(arr)=="[object Array]"}},{}],25:[function(_dereq_,module,exports){var s=1e3;var m=s*60;var h=m*60;var d=h*24;var y=d*365.25;module.exports=function(val,options){options=options||{};if("string"==typeof val)return parse(val);return options.long?long(val):short(val)};function parse(str){str=""+str;if(str.length>1e4)return;var match=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);if(!match)return;var n=parseFloat(match[1]);var type=(match[2]||"ms").toLowerCase();switch(type){case"years":case"year":case"yrs":case"yr":case"y":return n*y;case"days":case"day":case"d":return n*d;case"hours":case"hour":case"hrs":case"hr":case"h":return n*h;case"minutes":case"minute":case"mins":case"min":case"m":return n*m;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;cas
 e"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n}}function short(ms){if(ms>=d)return Math.round(ms/d)+"d";if(ms>=h)return Math.round(ms/h)+"h";if(ms>=m)return Math.round(ms/m)+"m";if(ms>=s)return Math.round(ms/s)+"s";return ms+"ms"}function long(ms){return plural(ms,d,"day")||plural(ms,h,"hour")||plural(ms,m,"minute")||plural(ms,s,"second")||ms+" ms"}function plural(ms,n,name){if(ms<n)return;if(ms<n*1.5)return Math.floor(ms/n)+" "+name;return Math.ceil(ms/n)+" "+name+"s"}},{}],26:[function(_dereq_,module,exports){(function(global){var rvalidchars=/^[\],:{}\s]*$/;var rvalidescape=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rvalidtokens=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rvalidbraces=/(?:^|:|,)(?:\s*\[)+/g;var rtrimLeft=/^\s+/;var rtrimRight=/\s+$/;module.exports=function parsejson(data){if("string"!=typeof data||!data){return null}data=data.replace(rtrimLeft,"").replace(rtrimRight,"");if(global.JSON&&JSON.parse){return JSON.
 parse(data)}if(rvalidchars.test(data.replace(rvalidescape,"@").replace(rvalidtokens,"]").replace(rvalidbraces,""))){return new Function("return "+data)()}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],27:[function(_dereq_,module,exports){exports.encode=function(obj){var str="";for(var i in obj){if(obj.hasOwnProperty(i)){if(str.length)str+="&";str+=encodeURIComponent(i)+"="+encodeURIComponent(obj[i])}}return str};exports.decode=function(qs){var qry={};var pairs=qs.split("&");for(var i=0,l=pairs.length;i<l;i++){var pair=pairs[i].split("=");qry[decodeURIComponent(pair[0])]=decodeURIComponent(pair[1])}return qry}},{}],28:[function(_dereq_,module,exports){var re=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;var parts=["source",
 "protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];module.exports=function parseuri(str){var src=str,b=str.indexOf("["),e=str.indexOf("]");if(b!=-1&&e!=-1){str=str.substring(0,b)+str.substring(b,e).replace(/:/g,";")+str.substring(e,str.length)}var m=re.exec(str||""),uri={},i=14;while(i--){uri[parts[i]]=m[i]||""}if(b!=-1&&e!=-1){uri.source=src;uri.host=uri.host.substring(1,uri.host.length-1).replace(/;/g,":");uri.authority=uri.authority.replace("[","").replace("]","").replace(/;/g,":");uri.ipv6uri=true}return uri}},{}],29:[function(_dereq_,module,exports){(function(global){(function(root){var freeExports=typeof exports=="object"&&exports;var freeModule=typeof module=="object"&&module&&module.exports==freeExports&&module;var freeGlobal=typeof global=="object"&&global;if(freeGlobal.global===freeGlobal||freeGlobal.window===freeGlobal){root=freeGlobal}var stringFromCharCode=String.fromCharCode;function ucs2decode(strin
 g){var output=[];var counter=0;var length=string.length;var value;var extra;while(counter<length){value=string.charCodeAt(counter++);if(value>=55296&&value<=56319&&counter<length){extra=string.charCodeAt(counter++);if((extra&64512)==56320){output.push(((value&1023)<<10)+(extra&1023)+65536)}else{output.push(value);counter--}}else{output.push(value)}}return output}function ucs2encode(array){var length=array.length;var index=-1;var value;var output="";while(++index<length){value=array[index];if(value>65535){value-=65536;output+=stringFromCharCode(value>>>10&1023|55296);value=56320|value&1023}output+=stringFromCharCode(value)}return output}function checkScalarValue(codePoint){if(codePoint>=55296&&codePoint<=57343){throw Error("Lone surrogate U+"+codePoint.toString(16).toUpperCase()+" is not a scalar value")}}function createByte(codePoint,shift){return stringFromCharCode(codePoint>>shift&63|128)}function encodeCodePoint(codePoint){if((codePoint&4294967168)==0){return stringFromCharCode(c
 odePoint)}var symbol="";if((codePoint&4294965248)==0){symbol=stringFromCharCode(codePoint>>6&31|192)}else if((codePoint&4294901760)==0){checkScalarValue(codePoint);symbol=stringFromCharCode(codePoint>>12&15|224);symbol+=createByte(codePoint,6)}else if((codePoint&4292870144)==0){symbol=stringFromCharCode(codePoint>>18&7|240);symbol+=createByte(codePoint,12);symbol+=createByte(codePoint,6)}symbol+=stringFromCharCode(codePoint&63|128);return symbol}function utf8encode(string){var codePoints=ucs2decode(string);var length=codePoints.length;var index=-1;var codePoint;var byteString="";while(++index<length){codePoint=codePoints[index];byteString+=encodeCodePoint(codePoint)}return byteString}function readContinuationByte(){if(byteIndex>=byteCount){throw Error("Invalid byte index")}var continuationByte=byteArray[byteIndex]&255;byteIndex++;if((continuationByte&192)==128){return continuationByte&63}throw Error("Invalid continuation byte")}function decodeSymbol(){var byte1;var byte2;var byte3;v
 ar byte4;var codePoint;if(byteIndex>byteCount){throw Error("Invalid byte index")}if(byteIndex==byteCount){return false}byte1=byteArray[byteIndex]&255;byteIndex++;if((byte1&128)==0){return byte1}if((byte1&224)==192){var byte2=readContinuationByte();codePoint=(byte1&31)<<6|byte2;if(codePoint>=128){return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&240)==224){byte2=readContinuationByte();byte3=readContinuationByte();codePoint=(byte1&15)<<12|byte2<<6|byte3;if(codePoint>=2048){checkScalarValue(codePoint);return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&248)==240){byte2=readContinuationByte();byte3=readContinuationByte();byte4=readContinuationByte();codePoint=(byte1&15)<<18|byte2<<12|byte3<<6|byte4;if(codePoint>=65536&&codePoint<=1114111){return codePoint}}throw Error("Invalid UTF-8 detected")}var byteArray;var byteCount;var byteIndex;function utf8decode(byteString){byteArray=ucs2decode(byteString);byteCount=byteArray.length;byteIndex=0;var 
 codePoints=[];var tmp;while((tmp=decodeSymbol())!==false){codePoints.push(tmp)}return ucs2encode(codePoints)}var utf8={version:"2.0.0",encode:utf8encode,decode:utf8decode};if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){define(function(){return utf8})}else if(freeExports&&!freeExports.nodeType){if(freeModule){freeModule.exports=utf8}else{var object={};var hasOwnProperty=object.hasOwnProperty;for(var key in utf8){hasOwnProperty.call(utf8,key)&&(freeExports[key]=utf8[key])}}}else{root.utf8=utf8}})(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],30:[function(_dereq_,module,exports){"use strict";var alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),length=64,map={},seed=0,i=0,prev;function encode(num){var encoded="";do{encoded=alphabet[num%length]+encoded;num=Math.floor(num/length)}while(num>0);return encoded}function decode(str){var decoded=0;for(i=
 0;i<str.length;i++){decoded=decoded*length+map[str.charAt(i)]}return decoded}function yeast(){var now=encode(+new Date);if(now!==prev)return seed=0,prev=now;return now+"."+encode(seed++)}for(;i<length;i++)map[alphabet[i]]=i;yeast.encode=encode;yeast.decode=decode;module.exports=yeast},{}],31:[function(_dereq_,module,exports){var url=_dereq_("./url");var parser=_dereq_("socket.io-parser");var Manager=_dereq_("./manager");var debug=_dereq_("debug")("socket.io-client");module.exports=exports=lookup;var cache=exports.managers={};function lookup(uri,opts){if(typeof uri=="object"){opts=uri;uri=undefined}opts=opts||{};var parsed=url(uri);var source=parsed.source;var id=parsed.id;var path=parsed.path;var sameNamespace=cache[id]&&path in cache[id].nsps;var newConnection=opts.forceNew||opts["force new connection"]||false===opts.multiplex||sameNamespace;var io;if(newConnection){debug("ignoring socket cache for %s",source);io=Manager(source,opts)}else{if(!cache[id]){debug("new io instance for %
 s",source);cache[id]=Manager(source,opts)}io=cache[id]}return io.socket(parsed.path)}exports.protocol=parser.protocol;exports.connect=lookup;exports.Manager=_dereq_("./manager");exports.Socket=_dereq_("./socket")},{"./manager":32,"./socket":34,"./url":35,debug:39,"socket.io-parser":47}],32:[function(_dereq_,module,exports){var eio=_dereq_("engine.io-client");var Socket=_dereq_("./socket");var Emitter=_dereq_("component-emitter");var parser=_dereq_("socket.io-parser");var on=_dereq_("./on");var bind=_dereq_("component-bind");var debug=_dereq_("debug")("socket.io-client:manager");var indexOf=_dereq_("indexof");var Backoff=_dereq_("backo2");var has=Object.prototype.hasOwnProperty;module.exports=Manager;function Manager(uri,opts){if(!(this instanceof Manager))return new Manager(uri,opts);if(uri&&"object"==typeof uri){opts=uri;uri=undefined}opts=opts||{};opts.path=opts.path||"/socket.io";this.nsps={};this.subs=[];this.opts=opts;this.reconnection(opts.reconnection!==false);this.reconnecti
 onAttempts(opts.reconnectionAttempts||Infinity);this.reconnectionDelay(opts.reconnectionDelay||1e3);this.reconnectionDelayMax(opts.reconnectionDelayMax||5e3);this.randomizationFactor(opts.randomizationFactor||.5);this.backoff=new Backoff({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()});this.timeout(null==opts.timeout?2e4:opts.timeout);this.readyState="closed";this.uri=uri;this.connecting=[];this.lastPing=null;this.encoding=false;this.packetBuffer=[];this.encoder=new parser.Encoder;this.decoder=new parser.Decoder;this.autoConnect=opts.autoConnect!==false;if(this.autoConnect)this.open()}Manager.prototype.emitAll=function(){this.emit.apply(this,arguments);for(var nsp in this.nsps){if(has.call(this.nsps,nsp)){this.nsps[nsp].emit.apply(this.nsps[nsp],arguments)}}};Manager.prototype.updateSocketIds=function(){for(var nsp in this.nsps){if(has.call(this.nsps,nsp)){this.nsps[nsp].id=this.engine.id}}};Emitter(Manager.prototype);Manager.prototyp
 e.reconnection=function(v){if(!arguments.length)return this._reconnection;this._reconnection=!!v;return this};Manager.prototype.reconnectionAttempts=function(v){if(!arguments.length)return this._reconnectionAttempts;this._reconnectionAttempts=v;return this};Manager.prototype.reconnectionDelay=function(v){if(!arguments.length)return this._reconnectionDelay;this._reconnectionDelay=v;this.backoff&&this.backoff.setMin(v);return this};Manager.prototype.randomizationFactor=function(v){if(!arguments.length)return this._randomizationFactor;this._randomizationFactor=v;this.backoff&&this.backoff.setJitter(v);return this};Manager.prototype.reconnectionDelayMax=function(v){if(!arguments.length)return this._reconnectionDelayMax;this._reconnectionDelayMax=v;this.backoff&&this.backoff.setMax(v);return this};Manager.prototype.timeout=function(v){if(!arguments.length)return this._timeout;this._timeout=v;return this};Manager.prototype.maybeReconnectOnOpen=function(){if(!this.reconnecting&&this._recon
 nection&&this.backoff.attempts===0){this.reconnect()}};Manager.prototype.open=Manager.prototype.connect=function(fn){debug("readyState %s",this.readyState);if(~this.readyState.indexOf("open"))return this;debug("opening %s",this.uri);this.engine=eio(this.uri,this.opts);var socket=this.engine;var self=this;this.readyState="opening";this.skipReconnect=false;var openSub=on(socket,"open",function(){self.onopen();fn&&fn()});var errorSub=on(socket,"error",function(data){debug("connect_error");self.cleanup();self.readyState="closed";self.emitAll("connect_error",data);if(fn){var err=new Error("Connection error");err.data=data;fn(err)}else{self.maybeReconnectOnOpen()}});if(false!==this._timeout){var timeout=this._timeout;debug("connect attempt will timeout after %d",timeout);var timer=setTimeout(function(){debug("connect attempt timed out after %d",timeout);openSub.destroy();socket.close();socket.emit("error","timeout");self.emitAll("connect_timeout",timeout)},timeout);this.subs.push({destroy
 :function(){clearTimeout(timer)}})}this.subs.push(openSub);this.subs.push(errorSub);return this};Manager.prototype.onopen=function(){debug("open");this.cleanup();this.readyState="open";this.emit("open");var socket=this.engine;this.subs.push(on(socket,"data",bind(this,"ondata")));this.subs.push(on(socket,"ping",bind(this,"onping")));this.subs.push(on(socket,"pong",bind(this,"onpong")));this.subs.push(on(socket,"error",bind(this,"onerror")));this.subs.push(on(socket,"close",bind(this,"onclose")));this.subs.push(on(this.decoder,"decoded",bind(this,"ondecoded")))};Manager.prototype.onping=function(){this.lastPing=new Date;this.emitAll("ping")};Manager.prototype.onpong=function(){this.emitAll("pong",new Date-this.lastPing)};Manager.prototype.ondata=function(data){this.decoder.add(data)};Manager.prototype.ondecoded=function(packet){this.emit("packet",packet)};Manager.prototype.onerror=function(err){debug("error",err);this.emitAll("error",err)};Manager.prototype.socket=function(nsp){var so
 cket=this.nsps[nsp];if(!socket){socket=new Socket(this,nsp);this.nsps[nsp]=socket;var self=this;socket.on("connecting",onConnecting);
+socket.on("connect",function(){socket.id=self.engine.id});if(this.autoConnect){onConnecting()}}function onConnecting(){if(!~indexOf(self.connecting,socket)){self.connecting.push(socket)}}return socket};Manager.prototype.destroy=function(socket){var index=indexOf(this.connecting,socket);if(~index)this.connecting.splice(index,1);if(this.connecting.length)return;this.close()};Manager.prototype.packet=function(packet){debug("writing packet %j",packet);var self=this;if(!self.encoding){self.encoding=true;this.encoder.encode(packet,function(encodedPackets){for(var i=0;i<encodedPackets.length;i++){self.engine.write(encodedPackets[i],packet.options)}self.encoding=false;self.processPacketQueue()})}else{self.packetBuffer.push(packet)}};Manager.prototype.processPacketQueue=function(){if(this.packetBuffer.length>0&&!this.encoding){var pack=this.packetBuffer.shift();this.packet(pack)}};Manager.prototype.cleanup=function(){debug("cleanup");var sub;while(sub=this.subs.shift())sub.destroy();this.pac
 ketBuffer=[];this.encoding=false;this.lastPing=null;this.decoder.destroy()};Manager.prototype.close=Manager.prototype.disconnect=function(){debug("disconnect");this.skipReconnect=true;this.reconnecting=false;if("opening"==this.readyState){this.cleanup()}this.backoff.reset();this.readyState="closed";if(this.engine)this.engine.close()};Manager.prototype.onclose=function(reason){debug("onclose");this.cleanup();this.backoff.reset();this.readyState="closed";this.emit("close",reason);if(this._reconnection&&!this.skipReconnect){this.reconnect()}};Manager.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var self=this;if(this.backoff.attempts>=this._reconnectionAttempts){debug("reconnect failed");this.backoff.reset();this.emitAll("reconnect_failed");this.reconnecting=false}else{var delay=this.backoff.duration();debug("will wait %dms before reconnect attempt",delay);this.reconnecting=true;var timer=setTimeout(function(){if(self.skipReconnect)return;debug("at
 tempting reconnect");self.emitAll("reconnect_attempt",self.backoff.attempts);self.emitAll("reconnecting",self.backoff.attempts);if(self.skipReconnect)return;self.open(function(err){if(err){debug("reconnect attempt error");self.reconnecting=false;self.reconnect();self.emitAll("reconnect_error",err.data)}else{debug("reconnect success");self.onreconnect()}})},delay);this.subs.push({destroy:function(){clearTimeout(timer)}})}};Manager.prototype.onreconnect=function(){var attempt=this.backoff.attempts;this.reconnecting=false;this.backoff.reset();this.updateSocketIds();this.emitAll("reconnect",attempt)}},{"./on":33,"./socket":34,backo2:36,"component-bind":37,"component-emitter":38,debug:39,"engine.io-client":1,indexof:42,"socket.io-parser":47}],33:[function(_dereq_,module,exports){module.exports=on;function on(obj,ev,fn){obj.on(ev,fn);return{destroy:function(){obj.removeListener(ev,fn)}}}},{}],34:[function(_dereq_,module,exports){var parser=_dereq_("socket.io-parser");var Emitter=_dereq_("
 component-emitter");var toArray=_dereq_("to-array");var on=_dereq_("./on");var bind=_dereq_("component-bind");var debug=_dereq_("debug")("socket.io-client:socket");var hasBin=_dereq_("has-binary");module.exports=exports=Socket;var events={connect:1,connect_error:1,connect_timeout:1,connecting:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1,ping:1,pong:1};var emit=Emitter.prototype.emit;function Socket(io,nsp){this.io=io;this.nsp=nsp;this.json=this;this.ids=0;this.acks={};this.receiveBuffer=[];this.sendBuffer=[];this.connected=false;this.disconnected=true;if(this.io.autoConnect)this.open()}Emitter(Socket.prototype);Socket.prototype.subEvents=function(){if(this.subs)return;var io=this.io;this.subs=[on(io,"open",bind(this,"onopen")),on(io,"packet",bind(this,"onpacket")),on(io,"close",bind(this,"onclose"))]};Socket.prototype.open=Socket.prototype.connect=function(){if(this.connected)return this;this.subEvents();this.io.open();if
 ("open"==this.io.readyState)this.onopen();this.emit("connecting");return this};Socket.prototype.send=function(){var args=toArray(arguments);args.unshift("message");this.emit.apply(this,args);return this};Socket.prototype.emit=function(ev){if(events.hasOwnProperty(ev)){emit.apply(this,arguments);return this}var args=toArray(arguments);var parserType=parser.EVENT;if(hasBin(args)){parserType=parser.BINARY_EVENT}var packet={type:parserType,data:args};packet.options={};packet.options.compress=!this.flags||false!==this.flags.compress;if("function"==typeof args[args.length-1]){debug("emitting packet with ack id %d",this.ids);this.acks[this.ids]=args.pop();packet.id=this.ids++}if(this.connected){this.packet(packet)}else{this.sendBuffer.push(packet)}delete this.flags;return this};Socket.prototype.packet=function(packet){packet.nsp=this.nsp;this.io.packet(packet)};Socket.prototype.onopen=function(){debug("transport is open - connecting");if("/"!=this.nsp){this.packet({type:parser.CONNECT})}};
 Socket.prototype.onclose=function(reason){debug("close (%s)",reason);this.connected=false;this.disconnected=true;delete this.id;this.emit("disconnect",reason)};Socket.prototype.onpacket=function(packet){if(packet.nsp!=this.nsp)return;switch(packet.type){case parser.CONNECT:this.onconnect();break;case parser.EVENT:this.onevent(packet);break;case parser.BINARY_EVENT:this.onevent(packet);break;case parser.ACK:this.onack(packet);break;case parser.BINARY_ACK:this.onack(packet);break;case parser.DISCONNECT:this.ondisconnect();break;case parser.ERROR:this.emit("error",packet.data);break}};Socket.prototype.onevent=function(packet){var args=packet.data||[];debug("emitting event %j",args);if(null!=packet.id){debug("attaching ack callback to event");args.push(this.ack(packet.id))}if(this.connected){emit.apply(this,args)}else{this.receiveBuffer.push(args)}};Socket.prototype.ack=function(id){var self=this;var sent=false;return function(){if(sent)return;sent=true;var args=toArray(arguments);debug
 ("sending ack %j",args);var type=hasBin(args)?parser.BINARY_ACK:parser.ACK;self.packet({type:type,id:id,data:args})}};Socket.prototype.onack=function(packet){var ack=this.acks[packet.id];if("function"==typeof ack){debug("calling ack %s with %j",packet.id,packet.data);ack.apply(this,packet.data);delete this.acks[packet.id]}else{debug("bad ack %s",packet.id)}};Socket.prototype.onconnect=function(){this.connected=true;this.disconnected=false;this.emit("connect");this.emitBuffered()};Socket.prototype.emitBuffered=function(){var i;for(i=0;i<this.receiveBuffer.length;i++){emit.apply(this,this.receiveBuffer[i])}this.receiveBuffer=[];for(i=0;i<this.sendBuffer.length;i++){this.packet(this.sendBuffer[i])}this.sendBuffer=[]};Socket.prototype.ondisconnect=function(){debug("server disconnect (%s)",this.nsp);this.destroy();this.onclose("io server disconnect")};Socket.prototype.destroy=function(){if(this.subs){for(var i=0;i<this.subs.length;i++){this.subs[i].destroy()}this.subs=null}this.io.destro
 y(this)};Socket.prototype.close=Socket.prototype.disconnect=function(){if(this.connected){debug("performing disconnect (%s)",this.nsp);this.packet({type:parser.DISCONNECT})}this.destroy();if(this.connected){this.onclose("io client disconnect")}return this};Socket.prototype.compress=function(compress){this.flags=this.flags||{};this.flags.compress=compress;return this}},{"./on":33,"component-bind":37,"component-emitter":38,debug:39,"has-binary":41,"socket.io-parser":47,"to-array":51}],35:[function(_dereq_,module,exports){(function(global){var parseuri=_dereq_("parseuri");var debug=_dereq_("debug")("socket.io-client:url");module.exports=url;function url(uri,loc){var obj=uri;var loc=loc||global.location;if(null==uri)uri=loc.protocol+"//"+loc.host;if("string"==typeof uri){if("/"==uri.charAt(0)){if("/"==uri.charAt(1)){uri=loc.protocol+uri}else{uri=loc.host+uri}}if(!/^(https?|wss?):\/\//.test(uri)){debug("protocol-less url %s",uri);if("undefined"!=typeof loc){uri=loc.protocol+"//"+uri}else
 {uri="https://"+uri}}debug("parse %s",uri);obj=parseuri(uri)}if(!obj.port){if(/^(http|ws)$/.test(obj.protocol)){obj.port="80"}else if(/^(http|ws)s$/.test(obj.protocol)){obj.port="443"}}obj.path=obj.path||"/";var ipv6=obj.host.indexOf(":")!==-1;var host=ipv6?"["+obj.host+"]":obj.host;obj.id=obj.protocol+"://"+host+":"+obj.port;obj.href=obj.protocol+"://"+host+(loc&&loc.port==obj.port?"":":"+obj.port);return obj}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{debug:39,parseuri:45}],36:[function(_dereq_,module,exports){module.exports=Backoff;function Backoff(opts){opts=opts||{};this.ms=opts.min||100;this.max=opts.max||1e4;this.factor=opts.factor||2;this.jitter=opts.jitter>0&&opts.jitter<=1?opts.jitter:0;this.attempts=0}Backoff.prototype.duration=function(){var ms=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var rand=Math.random();var deviation=Math.floor(rand*this.jitter*ms);ms=(Math.floor(rand*10)
 &1)==0?ms-deviation:ms+deviation}return Math.min(ms,this.max)|0};Backoff.prototype.reset=function(){this.attempts=0};Backoff.prototype.setMin=function(min){this.ms=min};Backoff.prototype.setMax=function(max){this.max=max};Backoff.prototype.setJitter=function(jitter){this.jitter=jitter}},{}],37:[function(_dereq_,module,exports){var slice=[].slice;module.exports=function(obj,fn){if("string"==typeof fn)fn=obj[fn];if("function"!=typeof fn)throw new Error("bind() requires a function");var args=slice.call(arguments,2);return function(){return fn.apply(obj,args.concat(slice.call(arguments)))}}},{}],38:[function(_dereq_,module,exports){module.exports=Emitter;function Emitter(obj){if(obj)return mixin(obj)}function mixin(obj){for(var key in Emitter.prototype){obj[key]=Emitter.prototype[key]}return obj}Emitter.prototype.on=Emitter.prototype.addEventListener=function(event,fn){this._callbacks=this._callbacks||{};(this._callbacks["$"+event]=this._callbacks["$"+event]||[]).push(fn);return this};E
 mitter.prototype.once=function(event,fn){function on(){this.off(event,on);fn.apply(this,arguments)}on.fn=fn;this.on(event,on);return this};Emitter.prototype.off=Emitter.prototype.removeListener=Emitter.prototype.removeAllListeners=Emitter.prototype.removeEventListener=function(event,fn){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this}var callbacks=this._callbacks["$"+event];if(!callbacks)return this;if(1==arguments.length){delete this._callbacks["$"+event];return this}var cb;for(var i=0;i<callbacks.length;i++){cb=callbacks[i];if(cb===fn||cb.fn===fn){callbacks.splice(i,1);break}}return this};Emitter.prototype.emit=function(event){this._callbacks=this._callbacks||{};var args=[].slice.call(arguments,1),callbacks=this._callbacks["$"+event];if(callbacks){callbacks=callbacks.slice(0);for(var i=0,len=callbacks.length;i<len;++i){callbacks[i].apply(this,args)}}return this};Emitter.prototype.listeners=function(event){this._callbacks=this._callbacks||
 {};return this._callbacks["$"+event]||[]};Emitter.prototype.hasListeners=function(event){return!!this.listeners(event).length}},{}],39:[function(_dereq_,module,exports){arguments[4][17][0].apply(exports,arguments)},{"./debug":40,dup:17}],40:[function(_dereq_,module,exports){arguments[4][18][0].apply(exports,arguments)},{dup:18,ms:44}],41:[function(_dereq_,module,exports){(function(global){var isArray=_dereq_("isarray");module.exports=hasBinary;function hasBinary(data){function _hasBinary(obj){if(!obj)return false;if(global.Buffer&&global.Buffer.isBuffer&&global.Buffer.isBuffer(obj)||global.ArrayBuffer&&obj instanceof ArrayBuffer||global.Blob&&obj instanceof Blob||global.File&&obj instanceof File){return true}if(isArray(obj)){for(var i=0;i<obj.length;i++){if(_hasBinary(obj[i])){return true}}}else if(obj&&"object"==typeof obj){if(obj.toJSON&&"function"==typeof obj.toJSON){obj=obj.toJSON()}for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key)&&_hasBinary(obj[key])){retur
 n true}}}return false}return _hasBinary(data)}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{isarray:43}],42:[function(_dereq_,module,exports){arguments[4][23][0].apply(exports,arguments)},{dup:23}],43:[function(_dereq_,module,exports){arguments[4][24][0].apply(exports,arguments)},{dup:24}],44:[function(_dereq_,module,exports){arguments[4][25][0].apply(exports,arguments)},{dup:25}],45:[function(_dereq_,module,exports){arguments[4][28][0].apply(exports,arguments)},{dup:28}],46:[function(_dereq_,module,exports){(function(global){var isArray=_dereq_("isarray");var isBuf=_dereq_("./is-buffer");exports.deconstructPacket=function(packet){var buffers=[];var packetData=packet.data;function _deconstructPacket(data){if(!data)return data;if(isBuf(data)){var placeholder={_placeholder:true,num:buffers.length};buffers.push(data);return placeholder}else if(isArray(data)){var newData=new Array(data.length);for(var i=0;i<data.l
 ength;i++){newData[i]=_deconstructPacket(data[i])}return newData}else if("object"==typeof data&&!(data instanceof Date)){var newData={};for(var key in data){newData[key]=_deconstructPacket(data[key])}return newData}return data}var pack=packet;pack.data=_deconstructPacket(packetData);pack.attachments=buffers.length;return{packet:pack,buffers:buffers}};exports.reconstructPacket=function(packet,buffers){var curPlaceHolder=0;function _reconstructPacket(data){if(data&&data._placeholder){var buf=buffers[data.num];return buf}else if(isArray(data)){for(var i=0;i<data.length;i++){data[i]=_reconstructPacket(data[i])}return data}else if(data&&"object"==typeof data){for(var key in data){data[key]=_reconstructPacket(data[key])}return data}return data}packet.data=_reconstructPacket(packet.data);packet.attachments=undefined;return packet};exports.removeBlobs=function(data,callback){function _removeBlobs(obj,curKey,containingObject){if(!obj)return obj;if(global.Blob&&obj instanceof Blob||global.Fil
 e&&obj instanceof File){pendingBlobs++;var fileReader=new FileReader;fileReader.onload=function(){if(containingObject){containingObject[curKey]=this.result}else{bloblessData=this.result}if(!--pendingBlobs){callback(bloblessData)}};fileReader.readAsArrayBuffer(obj)}else if(isArray(obj)){for(var i=0;i<obj.length;i++){_removeBlobs(obj[i],i,obj)}}else if(obj&&"object"==typeof obj&&!isBuf(obj)){for(var key in obj){_removeBlobs(obj[key],key,obj)}}}var pendingBlobs=0;var bloblessData=data;_removeBlobs(bloblessData);if(!pendingBlobs){callback(bloblessData)}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./is-buffer":48,isarray:43}],47:[function(_dereq_,module,exports){var debug=_dereq_("debug")("socket.io-parser");var json=_dereq_("json3");var isArray=_dereq_("isarray");var Emitter=_dereq_("component-emitter");var binary=_dereq_("./binary");var isBuf=_dereq_("./is-buffer");exports.protocol=4;exports.types=["CONNECT","
 DISCONNECT","EVENT","BINARY_EVENT","ACK","BINARY_ACK","ERROR"];exports.CONNECT=0;exports.DISCONNECT=1;exports.EVENT=2;exports.ACK=3;exports.ERROR=4;exports.BINARY_EVENT=5;exports.BINARY_ACK=6;exports.Encoder=Encoder;exports.Decoder=Decoder;function Encoder(){}Encoder.prototype.encode=function(obj,callback){debug("encoding packet %j",obj);if(exports.BINARY_EVENT==obj.type||exports.BINARY_ACK==obj.type){encodeAsBinary(obj,callback)}else{var encoding=encodeAsString(obj);callback([encoding])}};function encodeAsString(obj){var str="";var nsp=false;str+=obj.type;if(exports.BINARY_EVENT==obj.type||exports.BINARY_ACK==obj.type){str+=obj.attachments;str+="-"}if(obj.nsp&&"/"!=obj.nsp){nsp=true;str+=obj.nsp}if(null!=obj.id){if(nsp){str+=",";nsp=false}str+=obj.id}if(null!=obj.data){if(nsp)str+=",";str+=json.stringify(obj.data)}debug("encoded %j as %s",obj,str);return str}function encodeAsBinary(obj,callback){function writeEncoding(bloblessData){var deconstruction=binary.deconstructPacket(bloble
 ssData);var pack=encodeAsString(deconstruction.packet);var buffers=deconstruction.buffers;buffers.unshift(pack);callback(buffers)}binary.removeBlobs(obj,writeEncoding)}function Decoder(){this.reconstructor=null}Emitter(Decoder.prototype);Decoder.prototype.add=function(obj){var packet;if("string"==typeof obj){packet=decodeString(obj);if(exports.BINARY_EVENT==packet.type||exports.BINARY_ACK==packet.type){this.reconstructor=new BinaryReconstructor(packet);if(this.reconstructor.reconPack.attachments===0){this.emit("decoded",packet)}}else{this.emit("decoded",packet)}}else if(isBuf(obj)||obj.base64){if(!this.reconstructor){throw new Error("got binary data when not reconstructing a packet")}else{packet=this.reconstructor.takeBinaryData(obj);if(packet){this.reconstructor=null;this.emit("decoded",packet)}}}else{throw new Error("Unknown type: "+obj)}};function decodeString(str){var p={};var i=0;p.type=Number(str.charAt(0));if(null==exports.types[p.type])return error();if(exports.BINARY_EVENT=
 =p.type||exports.BINARY_ACK==p.type){var buf="";while(str.charAt(++i)!="-"){buf+=str.charAt(i);if(i==str.length)break}if(buf!=Number(buf)||str.charAt(i)!="-"){throw new Error("Illegal attachments")}p.attachments=Number(buf)}if("/"==str.charAt(i+1)){p.nsp="";while(++i){var c=str.charAt(i);if(","==c)break;p.nsp+=c;if(i==str.length)break}}else{p.nsp="/"}var next=str.charAt(i+1);if(""!==next&&Number(next)==next){p.id="";while(++i){var c=str.charAt(i);if(null==c||Number(c)!=c){--i;break}p.id+=str.charAt(i);if(i==str.length)break}p.id=Number(p.id)}if(str.charAt(++i)){try{p.data=json.parse(str.substr(i))}catch(e){return error()}}debug("decoded %s as %j",str,p);return p}Decoder.prototype.destroy=function(){if(this.reconstructor){this.reconstructor.finishedReconstruction()}};function BinaryReconstructor(packet){this.reconPack=packet;this.buffers=[]}BinaryReconstructor.prototype.takeBinaryData=function(binData){this.buffers.push(binData);if(this.buffers.length==this.reconPack.attachments){var
  packet=binary.reconstructPacket(this.reconPack,this.buffers);this.finishedReconstruction();return packet}return null};BinaryReconstructor.prototype.finishedReconstruction=function(){this.reconPack=null;this.buffers=[]};function error(data){return{type:exports.ERROR,data:"parser error"}}},{"./binary":46,"./is-buffer":48,"component-emitter":49,debug:39,isarray:43,json3:50}],48:[function(_dereq_,module,exports){(function(global){module.exports=isBuf;function isBuf(obj){return global.Buffer&&global.Buffer.isBuffer(obj)||global.ArrayBuffer&&obj instanceof ArrayBuffer}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],49:[function(_dereq_,module,exports){arguments[4][15][0].apply(exports,arguments)},{dup:15}],50:[function(_dereq_,module,exports){(function(global){(function(){var isLoader=typeof define==="function"&&define.amd;var objectTypes={"function":true,object:true};var freeExports=objectTypes[typeof exports]&&e
 xports&&!exports.nodeType&&exports;var root=objectTypes[typeof window]&&window||this,freeGlobal=freeExports&&objectTypes[typeof module]&&module&&!module.nodeType&&typeof global=="object"&&global;if(freeGlobal&&(freeGlobal["global"]===freeGlobal||freeGlobal["window"]===freeGlobal||freeGlobal["self"]===freeGlobal)){root=freeGlobal}function runInContext(context,exports){context||(context=root["Object"]());exports||(exports=root["Object"]());var Number=context["Number"]||root["Number"],String=context["String"]||root["String"],Object=context["Object"]||root["Object"],Date=context["Date"]||root["Date"],SyntaxError=context["SyntaxError"]||root["SyntaxError"],TypeError=context["TypeError"]||root["TypeError"],Math=context["Math"]||root["Math"],nativeJSON=context["JSON"]||root["JSON"];if(typeof nativeJSON=="object"&&nativeJSON){exports.stringify=nativeJSON.stringify;exports.parse=nativeJSON.parse}var objectProto=Object.prototype,getClass=objectProto.toString,isProperty,forEach,undef;var isExt
 ended=new Date(-0xc782b5b800cec);try{isExtended=isExtended.getUTCFullYear()==-109252&&isExtended.getUTCMonth()===0&&isExtended.getUTCDate()===1&&isExtended.getUTCHours()==10&&isExtended.getUTCMinutes()==37&&isExtended.getUTCSeconds()==6&&isExtended.getUTCMilliseconds()==708}catch(exception){}function has(name){if(has[name]!==undef){return has[name]}var isSupported;if(name=="bug-string-char-index"){isSupported="a"[0]!="a"}else if(name=="json"){isSupported=has("json-stringify")&&has("json-parse")}else{var value,serialized='{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';if(name=="json-stringify"){var stringify=exports.stringify,stringifySupported=typeof stringify=="function"&&isExtended;if(stringifySupported){(value=function(){return 1}).toJSON=value;try{stringifySupported=stringify(0)==="0"&&stringify(new Number)==="0"&&stringify(new String)=='""'&&stringify(getClass)===undef&&stringify(undef)===undef&&stringify()===undef&&stringify(value)==="1"&&stringify([value])=="[1]"&&string
 ify([undef])=="[null]"&&stringify(null)=="null"&&stringify([undef,getClass,null])=="[null,null,null]"&&stringify({a:[value,true,false,null,"\x00\b\n\f\r	"]})==serialized&&stringify(null,value)==="1"&&stringify([1,2],null,1)=="[\n 1,\n 2\n]"&&stringify(new Date(-864e13))=='"-271821-04-20T00:00:00.000Z"'&&stringify(new Date(864e13))=='"+275760-09-13T00:00:00.000Z"'&&stringify(new Date(-621987552e5))=='"-000001-01-01T00:00:00.000Z"'&&stringify(new Date(-1))=='"1969-12-31T23:59:59.999Z"'}catch(exception){stringifySupported=false}}isSupported=stringifySupported}if(name=="json-parse"){var parse=exports.parse;if(typeof parse=="function"){try{if(parse("0")===0&&!parse(false)){value=parse(serialized);var parseSupported=value["a"].length==5&&value["a"][0]===1;if(parseSupported){try{parseSupported=!parse('"	"')}catch(exception){}if(parseSupported){try{parseSupported=parse("01")!==1}catch(exception){}}if(parseSupported){try{parseSupported=parse("1.")!==1}catch(exception){}}}}}catch(exception){p
 arseSupported=false}}isSupported=parseSupported}}return has[name]=!!isSupported}if(!has("json")){var functionClass="[object Function]",dateClass="[object Date]",numberClass="[object Number]",stringClass="[object String]",arrayClass="[object Array]",booleanClass="[object Boolean]";var charIndexBuggy=has("bug-string-char-index");if(!isExtended){var floor=Math.floor;var Months=[0,31,59,90,120,151,181,212,243,273,304,334];var getDay=function(year,month){return Months[month]+365*(year-1970)+floor((year-1969+(month=+(month>1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400)}}if(!(isProperty=objectProto.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={toString:1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result}}else{constructor=members.constructor;isProperty=function(property){var paren
 t=(this.constructor||constructor).prototype;return property in this&&!(property in parent&&this[property]===parent[property])}}members=null;return isProperty.call(this,property)}}forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0}).prototype.valueOf=0;members=new Properties;for(property in members){if(isProperty.call(members,property)){size++}}Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,length;var hasProperty=!isFunction&&typeof object.constructor!="function"&&objectTypes[typeof object.hasOwnProperty]&&object.hasOwnProperty||isProperty;for(property in object){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property)}}for(length=members.length;property=members[--length];hasProperty.call(ob
 ject,property)&&callback(property));}}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property)}}}}else{forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,isConstructor;for(property in object){if(!(isFunction&&property=="prototype")&&isProperty.call(object,property)&&!(isConstructor=property==="constructor")){callback(property)}}if(isConstructor||isProperty.call(object,property="constructor")){callback(property)}}}return forEach(object,callback)};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width)};var unicodePrefix="\\u00";var quote=funct
 ion(value){var result='"',index=0,length=value.length,useCharIndex=!charIndexBuggy||length>10;var symbols=useCharIndex&&(charIndexBugg

<TRUNCATED>

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[3/3] cordova-paramedic git commit: Adding real time logging and other improvements

Posted by sg...@apache.org.
Adding real time logging and other improvements

This closes #1


Project: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/commit/b1aa699d
Tree: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/tree/b1aa699d
Diff: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/diff/b1aa699d

Branch: refs/heads/master
Commit: b1aa699dfa56d442e75294448e3253ac6c7f0995
Parents: 8615f31
Author: Vladimir Kotikov <ko...@gmail.com>
Authored: Fri Mar 25 13:54:21 2016 +0300
Committer: sgrebnov <se...@gmail.com>
Committed: Fri Mar 25 17:04:26 2016 +0300

----------------------------------------------------------------------
 .jshintrc                                       |  14 +
 .travis.yml                                     |   1 +
 README.md                                       | 164 +++++++++-
 lib/LocalServer.js                              |  99 ++++++
 lib/ParamedicConfig.js                          |  83 +++++
 lib/PluginsManager.js                           |  51 ++++
 lib/Target.js                                   |   9 +
 lib/TestsRunner.js                              | 121 ++++++++
 lib/Tunnel.js                                   |  19 ++
 lib/logger.js                                   | 150 +++++++++
 lib/paramedic.js                                | 156 ++++++++++
 lib/portScanner.js                              |  54 ++++
 lib/reporters/ConsoleReporter.js                | 153 ++++++++++
 lib/reporters/ParamedicReporter.js              |  43 +++
 lib/specReporters.js                            |  68 +++++
 lib/utils.js                                    |  22 ++
 main.js                                         |  69 +++--
 package.json                                    |  24 +-
 paramedic-plugin/JasmineParamedicProxy.js       |  56 ++++
 paramedic-plugin/paramedic.js                   |  74 +++++
 paramedic-plugin/plugin.xml                     |  37 +++
 paramedic-plugin/socket.io.js                   |   4 +
 paramedic.js                                    | 305 -------------------
 sample-config/.paramedic-core-plugins.config.js |  51 ++++
 sample-config/.paramedic.config.js              |  33 ++
 25 files changed, 1502 insertions(+), 358 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/.jshintrc
----------------------------------------------------------------------
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..e11705f
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,14 @@
+{
+    "node" : true
+  , "devel": true
+  , "bitwise": true
+  , "undef": true
+  , "trailing": true
+  , "quotmark": false
+  , "indent": 4
+  , "unused": "vars"
+  , "expr": true
+  , "latedef": "nofunc"
+  , "globals": {
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index 3c02467..aa2e054 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ install:
   - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config 
   - npm install cordova
   - npm install ios-sim
+  - npm install ios-deploy
   - npm install
   - npm link
 script:

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 305feeb..4dcc055 100644
--- a/README.md
+++ b/README.md
@@ -7,31 +7,167 @@ Runs cordova medic/buildbot tests locally.
 
 ... provides advanced levels of care at the point of illness or injury, including out of hospital treatment, and diagnostic services
 
-To install :
+# To install :
 ``` $npm install cordova-paramedic ```
 
-Usage :
+## Supported Cordova Platforms
+
+- Android
+- iOS
+- Windows Phone 8
+- Windows (Windows 8.1, Windows Phone 8.1, Windows 10 Tablet/PC)
+- Browser
+
+# Usage
+
+Paramedic parameters could be passed via command line arguments or via separate configuration file:
+
+```
+cordova-paramedic --platform PLATFORM --plugin PATH <other parameters>
+cordova-paramedic --config ./sample-config/.paramedic.config.js
+```
+
+## Command Line Interface
+
+####`--platform` (required)
+
+Specifies target cordova platform (could refer to local directory, npm or git)
+
+```
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser
+cordova-paramedic --platform ios@4.0 --plugin cordova-plugin-inappbrowser
+cordova-paramedic --platform ios@../cordova-ios --plugin cordova-plugin-inappbrowser
+cordova-paramedic --platform ios@https://github.com/apache/cordova-ios.git#4.1.0 --plugin cordova-plugin-inappbrowser
+```
+
+####`--plugin` (required)
+
+Specifies test plugin, you may specify multiple --plugin flags and they will all be installed and tested together. Similat to `platform` parameter you can refer to local (or absolute) path, npm registry or git repo.
 
 ```
-cordova-paramedic --platform PLATFORM --plugin PATH [--justbuild --timeout MSECS --port PORTNUM --browserify --verbose ]`PLATFORM` : the platform id, currently only supports 'ios'
-`PATH` : the relative or absolute path to a plugin folder
-	expected to have a 'tests' folder.
-	You may specify multiple --plugin flags and they will all
-	be installed and tested together.
-`MSECS` : (optional) time in millisecs to wait for tests to pass|fail 
-	(defaults to 10 minutes) 
-`PORTNUM` : (optional) port to use for posting results from emulator back to paramedic server
---justbuild : (optional) just builds the project, without running the tests 
---browserify : (optional) plugins are browserified into cordova.js 
---verbose : (optional) verbose mode. Display more information output
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser
+cordova-paramedic --platform ios --plugin ../cordova-plugin-inappbrowser
+cordova-paramedic --platform ios --plugin https://github.com/apache/cordova-plugin-inappbrowser
+// several plugins
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --plugin cordova-plugin-contacts
+```
+####--justbuild (optional)
+
+Just builds the project, without running the tests.
+
+```
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --justbuild
+```
+
+####--device (optional)
+
+Tests must be run on connected device instead of emulator.
+
+```
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --device
+```
+
+####--externalServerUrl (optional)
+
+Useful when testing on real device (`--device` parameter) so that tests results from device could be posted back to paramedic server.
+
+```
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --externalServerUrl http://10.0.8.254
+```
+
+####--useTunnel (optional)
+
+Use [tunneling](https://www.npmjs.com/package/localtunnel) instead of local address (default is false).
+Useful when testing on real devices and don't want to specify external ip address (see `--externalServerUrl` above) of paramedic server.
 
 ```
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --useTunnel
+```
+
+####--browserify (optional)
+
+Plugins are browserified into cordova.js.
+
+```
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --browserify
+```
+
+####--port (optional)
+
+Port to use for posting results from emulator back to paramedic server (default is from `8008`). You can also specify a range using `--startport` and `endport` and paramedic will select the first available.
+
+```
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --port 8010
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --startport 8000 endport 8020
+```
+
+####--verbose (optional)
+
+Verbose mode. Display more information output
+
+```
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --verbose
+```
+
+####--timeout (optional)
+
+Time in millisecs to wait for tests to pass|fail (defaults to 10 minutes). 
+
+```
+cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --timeout 30000
+```
+
+## Paramedic configuration file
+
+Configuration file is used when no parameters are passed to `cordova-paramedic` call or explicitly specified via `--config` parameter:
+```
+cordova-paramedic  <- paramedic will attempt to find .paramedic.config.js in working directory
+cordova-paramedic --config ./sample-config/.paramedic.config.js
+```
+Example configuration file is showed below. It supports similar arguments and has the following advantages over `Command Line Approach`:
+
+-   Supports extra arguments which could be passed to cordova so that you have full control over build and run target.
+-   Supports several test platforms (targets) to be executed as single paramedic run (results will be aggregated) so you don't need to re-install test plugins, create local server and do other steps several times.
+
+```
+module.exports = {
+    // "externalServerUrl": "http://10.0.8.254",
+    "useTunnel": true,
+    "plugins": [
+        "https://github.com/apache/cordova-plugin-inappbrowser"
+    ],
+    "targets": [
+        {
+            "platform": "ios@https://github.com/apache/cordova-ios.git",
+            "action": "run",
+             "args": "--device"
+        },
+        {
+            "platform": "android@https://github.com/apache/cordova-android.git",
+            "action": "run",
+             "args": "--device"
+        },
+        {    // Windows 8.1 Desktop(anycpu)
+            "platform": "windows@https://github.com/apache/cordova-windows.git",
+            "action": "run"
+        },
+        {   // Windows 10 Desktop(x64)
+            "platform": "windows@https://github.com/apache/cordova-windows.git",
+            "action": "run",
+            "args": "--archs=x64 -- --appx=uap"
+        }
+    ]
+}
+```
+More configuration file examples could be found in `sample-config` folder.
+
+## API Interface
 
 You can also use cordova-paramedic as a module directly :
 
 ```
   var paramedic = require('cordova-paramedic');
-  paramedic.run('ios', '../cordova-plugin-device', onCompleteCallback,justBuild,portNum,msTimeout, useBrowserify, beSilent, beVerbose);
+  paramedic.run(config);
 ```
 
 

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/LocalServer.js
----------------------------------------------------------------------
diff --git a/lib/LocalServer.js b/lib/LocalServer.js
new file mode 100644
index 0000000..a7bf42c
--- /dev/null
+++ b/lib/LocalServer.js
@@ -0,0 +1,99 @@
+var Q = require('q');
+var io = require('socket.io');
+
+var logger = require('./logger').get();
+
+var specReporters = require('./specReporters');
+
+
+function LocalServer(port, externalServerUrl, tunneledUrl) {
+    this.port = port;
+    this.tunneledUrl = tunneledUrl;
+    this.externalServerUrl = externalServerUrl;
+    this.onTestsResults = null;
+}
+
+LocalServer.startServer = function(port, externalServerUrl, tunneledUrl) {
+    var localServer = new LocalServer(port, externalServerUrl, tunneledUrl);
+    localServer.createSocketListener();
+    return Q.resolve(localServer);
+};
+
+LocalServer.prototype.createSocketListener = function() {
+    var listener = io.listen(this.port, {
+        pingTimeout: 60000, // how many ms without a pong packet to consider the connection closed
+        pingInterval: 25000 // how many ms before sending a new ping packet
+    });
+
+    var me  = this;
+
+    var routes = {
+        'log': me.onDeviceLog.bind(me),
+        'disconnect': me.onTestsCompletedOrDisconnected.bind(me),
+        'deviceInfo': me.onDeviceInfo.bind(me),
+        'jasmineStarted': specReporters.jasmineStarted.bind(specReporters),
+        'specStarted': specReporters.specStarted.bind(specReporters),
+        'specDone': specReporters.specDone.bind(specReporters),
+        'suiteStarted': specReporters.suiteStarted.bind(specReporters),
+        'suiteDone': specReporters.suiteDone.bind(specReporters),
+        'jasmineDone': me.onJasmineDone.bind(me)
+    };
+
+    listener.on('connection', function(socket) {
+        logger.info('local-server: new socket connection');
+        me.connection = socket;
+
+        for (var routeType in routes) {
+            socket.on(routeType, routes[routeType]);
+        }
+    });
+};
+
+LocalServer.prototype.haveConnectionUrl = function() {
+    return !!(this.tunneledUrl || this.externalServerUrl);
+};
+
+LocalServer.prototype.getConnectionUrl = function() {
+    return this.tunneledUrl || this.externalServerUrl + ":" + this.port;
+};
+
+LocalServer.prototype.reset = function() {
+    this.onTestsResults = null;
+    if (this.connection) {
+        this.connection.disconnect();
+        this.connection = null;
+    }
+
+    specReporters.reset();
+};
+
+LocalServer.prototype.onDeviceLog = function(data) {
+    logger.verbose('device|console.'+data.type + ': '  + data.msg[0]);
+};
+
+LocalServer.prototype.onDeviceInfo = function(data) {
+    logger.info('cordova-paramedic: Device info: ' + JSON.stringify(data));
+};
+
+LocalServer.prototype.onTestsCompleted = function(msg) {
+    logger.normal('local-server: tests completed');
+    this.lastMobileSpecResults = specReporters.getResults();
+};
+
+LocalServer.prototype.onJasmineDone = function(data) {
+    specReporters.jasmineDone(data);
+    // save results to report them later
+    this.onTestsCompleted();
+    // disconnect because all tests have been completed
+    this.connection.disconnect();
+};
+
+LocalServer.prototype.onTestsCompletedOrDisconnected = function() {
+    logger.info('local-server: tests have been completed or test device has disconnected');
+    if (this.onTestsResults) {
+        this.onTestsResults(this.lastMobileSpecResults);
+    }
+    this.lastMobileSpecResults = null;
+};
+
+module.exports = LocalServer;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/ParamedicConfig.js
----------------------------------------------------------------------
diff --git a/lib/ParamedicConfig.js b/lib/ParamedicConfig.js
new file mode 100644
index 0000000..d9acdec
--- /dev/null
+++ b/lib/ParamedicConfig.js
@@ -0,0 +1,83 @@
+var Target = require('./Target');
+
+var DEFAULT_START_PORT = 8008;
+var DEFAULT_END_PORT = 8018;
+var DEFAULT_TIMEOUT = 10 * 60 * 1000; // 10 minutes in msec - this will become a param
+
+function ParamedicConfig(json) {
+    this._config = json;
+}
+
+ParamedicConfig.parseFromArguments = function (argv) {
+    return new ParamedicConfig({
+        "targets": [{
+            platform: argv.platform,
+            action: !!argv.justbuild ? 'build' : 'run',
+            args: (!!argv.browserify ? '--browserify ' : '') + (!!argv.device ? '--device' : '')
+        }],
+        "plugins":   Array.isArray(argv.plugin) ? argv.plugin : [argv.plugin],
+        "useTunnel": !!argv.useTunnel,
+        "verbose":   !!argv.verbose,
+        "startPort": argv.startport || argv.port,
+        "endPort":   argv.endport || argv.port,
+        "externalServerUrl": argv.externalServerUrl,
+        "reportSavePath":  !!argv.reportSavePath? argv.reportSavePath: undefined,
+        "cleanUpAfterRun": !!argv.cleanUpAfterRun? true: false
+    });
+};
+
+ParamedicConfig.parseFromFile = function (paramedicConfigPath) {
+    return new ParamedicConfig(require(paramedicConfigPath));
+};
+
+ParamedicConfig.prototype.useTunnel = function () {
+    return this._config.useTunnel;
+
+};
+
+ParamedicConfig.prototype.getReportSavePath = function () {
+    return this._config.reportSavePath;
+};
+
+ParamedicConfig.prototype.shouldCleanUpAfterRun = function () {
+    return this._config.cleanUpAfterRun;
+};
+
+ParamedicConfig.prototype.getTargets = function () {
+    return this._config.targets.map(function(target) {
+        return new Target(target);
+    });
+};
+
+ParamedicConfig.prototype.getPlugins = function () {
+    return this._config.plugins;
+};
+
+ParamedicConfig.prototype.getExternalServerUrl= function () {
+    return this._config.externalServerUrl;
+};
+
+ParamedicConfig.prototype.isVerbose = function() {
+    return this._config.verbose;
+};
+
+ParamedicConfig.prototype.isJustBuild = function() {
+    return this._config.justbuild;
+};
+
+ParamedicConfig.prototype.isBrowserify = function() {
+    return this._config.browserify;
+};
+
+ParamedicConfig.prototype.getPorts = function() {
+    return {
+        start: this._config.startPort || DEFAULT_START_PORT,
+        end: this._config.endPort || DEFAULT_END_PORT
+    };
+};
+
+ParamedicConfig.prototype.getTimeout = function() {
+    return DEFAULT_TIMEOUT;
+};
+
+module.exports = ParamedicConfig;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/PluginsManager.js
----------------------------------------------------------------------
diff --git a/lib/PluginsManager.js b/lib/PluginsManager.js
new file mode 100644
index 0000000..3d4c441
--- /dev/null
+++ b/lib/PluginsManager.js
@@ -0,0 +1,51 @@
+var path = require('path');
+var fs = require('fs');
+var logger = require('./logger').get();
+var exec = require('./utils').exec;
+var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
+
+function PluginsManager(appRoot, storedCWD) {
+    this.appRoot = appRoot;
+    this.storedCWD = storedCWD;
+}
+
+PluginsManager.prototype.installPlugins = function(plugins) {
+    for(var n = 0; n < plugins.length; n++) {
+        var plugin = plugins[n];
+        this.installSinglePlugin(plugin);
+    }
+};
+
+PluginsManager.prototype.installTestsForExistingPlugins = function() {
+    var installedPlugins = new PluginInfoProvider().getAllWithinSearchPath(path.join(this.appRoot, 'plugins'));
+    var me = this;
+    installedPlugins.forEach(function(plugin) {
+        // there is test plugin available
+        if (fs.existsSync(path.join(plugin.dir, 'tests', 'plugin.xml'))) {
+            me.installSinglePlugin(path.join(plugin.dir, 'tests'));
+        }
+    });
+    this.showPluginsVersions();
+};
+
+PluginsManager.prototype.installSinglePlugin = function(plugin) {
+    if (fs.existsSync(path.resolve(this.storedCWD, plugin))) {
+        plugin = path.resolve(this.storedCWD, plugin);
+    }
+
+    logger.normal("cordova-paramedic: installing " + plugin);
+    // var pluginPath = path.resolve(this.storedCWD, plugin);
+
+    var plugAddCmd = exec('cordova plugin add ' + plugin);
+    if(plugAddCmd.code !== 0) {
+        logger.error('Failed to install plugin : ' + plugin);
+        throw new Error('Failed to install plugin : ' + plugin);
+    }
+};
+
+PluginsManager.prototype.showPluginsVersions = function() {
+    logger.verbose("cordova-paramedic: versions of installed plugins: ");
+    exec('cordova plugins');
+};
+
+module.exports = PluginsManager;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/Target.js
----------------------------------------------------------------------
diff --git a/lib/Target.js b/lib/Target.js
new file mode 100644
index 0000000..0cb5c53
--- /dev/null
+++ b/lib/Target.js
@@ -0,0 +1,9 @@
+
+function Target(config) {
+    this.platform = config.platform;
+    this.action =  config.action || 'run';
+    this.args = config.args || null;
+    this.platformId = this.platform.split("@")[0];
+}
+
+module.exports = Target;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/TestsRunner.js
----------------------------------------------------------------------
diff --git a/lib/TestsRunner.js b/lib/TestsRunner.js
new file mode 100644
index 0000000..8515b58
--- /dev/null
+++ b/lib/TestsRunner.js
@@ -0,0 +1,121 @@
+var Q = require('q');
+var fs = require('fs');
+var path = require('path');
+var exec = require('./utils').exec;
+var logger = require('./logger').get();
+
+var MAX_PENDING_TIME = 60000;
+
+function TestsRunner (server) {
+    this.server = server;
+}
+
+TestsRunner.prototype.runSingleTarget = function (target, savePath) {
+    logger.info("Running target: " + target.platform);
+
+    var me = this;
+
+    this.server.reset(savePath);
+
+    return this.prepareAppToRunTarget(target).then(function() {
+        return me.installPlatform(target);
+    }).then(function() {
+        return me.checkPlatform(target);
+    }).then(function() {
+        return me.runTests(target);
+    })
+    .then(function (results) {
+        logger.normal("Removing platform: " + target.platformId);
+        exec('cordova platform rm ' + target.platformId);
+
+        return results;
+    })
+    .catch(function(error) {return error;});
+};
+
+TestsRunner.prototype.prepareAppToRunTarget = function(target) {
+    var me = this;
+    return Q.Promise(function(resolve, reject) {
+        // if we know external url we can safely use it
+        if (me.server.haveConnectionUrl()) {
+            me.writeMedicLogUrl(me.server.getConnectionUrl());
+        } else {
+            // otherwise, we assume we use local PC and platforms emulators
+            switch(target.platformId) {
+                case "android":
+                    me.writeMedicLogUrl("http://10.0.2.2:" + me.server.port);
+                    break;
+                case "ios"     :
+                case "browser" :
+                case "windows" :
+                /* falls through */
+                default:
+                    me.writeMedicLogUrl("http://127.0.0.1:" + me.server.port);
+                    break;
+            }
+        }
+        resolve();
+    });
+};
+
+TestsRunner.prototype.installPlatform = function(target) {
+    return Q.Promise(function(resolve, reject) {
+        logger.normal("cordova-paramedic: adding platform : " + target.platform);
+        exec('cordova platform add ' + target.platform);
+        resolve();
+    });
+};
+
+TestsRunner.prototype.checkPlatform = function(target) {
+    return Q.Promise(function(resolve, reject) {
+        logger.normal("cordova-paramedic: checking requirements for platform " + target.platformId);
+        var result = exec('cordova requirements ' + target.platformId);
+        if (result.code !== 0) {
+            reject(new Error('Platform requirements check has failed! Skipping...'));
+        } else resolve();
+    });
+};
+
+TestsRunner.prototype.writeMedicLogUrl = function(url) {
+    logger.normal("cordova-paramedic: writing medic log url to project " + url);
+    var obj = {logurl:url};
+    fs.writeFileSync(path.join("www","medic.json"), JSON.stringify(obj));
+};
+
+TestsRunner.prototype.runTests = function(target) {
+    logger.normal('Starting tests');
+    var self = this;
+
+    var cmd = "cordova " + target.action + " " + target.platformId;
+    if (target.args) {
+        cmd += " " + target.args;
+    }
+
+    logger.normal('cordova-paramedic: running command ' + cmd);
+
+    return Q.Promise(function (resolve, reject) {
+        logger.normal('Waiting for tests result');
+
+        self.server.onTestsResults = function (results) {
+            resolve(results);
+        };
+
+        exec(cmd, function(code, output){
+                if(code) {
+                    reject(new Error("cordova build returned error code " + code));
+                }
+
+                var waitForTestResults = target.action === 'run' || target.action === 'emulate';
+
+                if (!waitForTestResults) resolve({passed: true}); // skip tests if it was justbuild
+
+                setTimeout(function(){
+                    if (!self.server.connection)
+                        reject(new Error("Seems like device not connected to local server in " + MAX_PENDING_TIME / 1000 + " secs. Skipping this platform..."));
+                }, MAX_PENDING_TIME);
+            }
+        );
+    });
+};
+
+module.exports = TestsRunner;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/Tunnel.js
----------------------------------------------------------------------
diff --git a/lib/Tunnel.js b/lib/Tunnel.js
new file mode 100644
index 0000000..bed31e5
--- /dev/null
+++ b/lib/Tunnel.js
@@ -0,0 +1,19 @@
+ var exec = require('./utils').exec;
+ var path = require('path');
+ var Q = require('Q');
+
+function Tunnel(port) {
+    this.port = port;
+}
+
+Tunnel.prototype.createTunnel = function() {
+    var self = this;
+    //TODO: use localtunnel module instead of shell
+    return Q.Promise(function(resolve, reject) {
+        exec(path.resolve(__dirname, '../node_modules/.bin/lt') + ' --port ' + self.port, null, function(output) {
+            resolve(output.split(' ')[3]);
+        });
+    });
+};
+
+module.exports = Tunnel;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/logger.js
----------------------------------------------------------------------
diff --git a/lib/logger.js b/lib/logger.js
new file mode 100644
index 0000000..6a525c9
--- /dev/null
+++ b/lib/logger.js
@@ -0,0 +1,150 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+var ansi = require('ansi');
+var EventEmitter = require('events').EventEmitter;
+var CordovaError = require('cordova-common').CordovaError;
+var EOL = require('os').EOL;
+
+var INSTANCE;
+
+function CordovaLogger () {
+    this.levels = {};
+    this.colors = {};
+    this.stdout = process.stdout;
+    this.stderr = process.stderr;
+
+    this.stdoutCursor = ansi(this.stdout);
+    this.stderrCursor = ansi(this.stderr);
+
+    this.addLevel('verbose', 1000, 'grey');
+    this.addLevel('normal' , 2000);
+    this.addLevel('warn'   , 2000, 'yellow');
+    this.addLevel('info'   , 3000, 'blue');
+    this.addLevel('error'  , 5000, 'red');
+    this.addLevel('results' , 10000);
+
+    this.setLevel('normal');
+}
+
+CordovaLogger.get = function () {
+    return INSTANCE || (INSTANCE = new CordovaLogger());
+};
+
+CordovaLogger.VERBOSE = 'verbose';
+CordovaLogger.NORMAL = 'normal';
+CordovaLogger.WARN = 'warn';
+CordovaLogger.INFO = 'info';
+CordovaLogger.ERROR = 'error';
+CordovaLogger.RESULTS = 'results';
+
+CordovaLogger.prototype.log = function (logLevel, message) {
+    // if there is no such logLevel defined, or provided level has
+    // less severity than active level, then just ignore this call and return
+    if (!this.levels[logLevel] || this.levels[logLevel] < this.levels[this.logLevel])
+        // return instance to allow to chain calls
+        return this;
+    var isVerbose = this.logLevel === 'verbose';
+    var cursor = this.stdoutCursor;
+
+    if(message instanceof Error || logLevel === CordovaLogger.ERROR) {
+        message = formatError(message, isVerbose);
+        cursor = this.stderrCursor;
+    }
+
+    var color = this.colors[logLevel];
+    if (color) {
+        cursor.bold().fg[color]();
+    }
+
+    cursor.write(message).reset().write(EOL);
+
+    return this;
+};
+
+CordovaLogger.prototype.addLevel = function (level, severity, color) {
+
+    this.levels[level] = severity;
+
+    if (color) {
+        this.colors[level] = color;
+    }
+
+    // Define own method with corresponding name
+    if (!this[level]) {
+        this[level] = this.log.bind(this, level);
+    }
+
+    return this;
+};
+
+CordovaLogger.prototype.setLevel = function (logLevel) {
+    this.logLevel = this.levels[logLevel] ? logLevel : CordovaLogger.NORMAL;
+
+    return this;
+};
+
+CordovaLogger.prototype.subscribe = function (eventEmitter) {
+
+    if (!(eventEmitter instanceof EventEmitter))
+        throw new Error('Must provide a valid EventEmitter instance to subscribe CordovaLogger to');
+
+    var self = this;
+
+    process.on('uncaughtException', function(err) {
+        self.error(err);
+        process.exit(1);
+    });
+
+    eventEmitter.on('verbose', self.verbose)
+        .on('log', self.normal)
+        .on('info', self.info)
+        .on('warn', self.warn)
+        .on('warning', self.warn)
+        // Set up event handlers for logging and results emitted as events.
+        .on('results', self.results);
+
+    return this;
+};
+
+function formatError(error, isVerbose) {
+    var message = '';
+
+    if(error instanceof CordovaError) {
+        message = error.toString(isVerbose);
+    } else if(error instanceof Error) {
+        if(isVerbose) {
+            message = error.stack;
+        } else {
+            message = error.message;
+        }
+    } else {
+        // Plain text error message
+        message = error;
+    }
+
+    if(message.toUpperCase().indexOf('ERROR:') !== 0) {
+        // Needed for backward compatibility with external tools
+        message = 'Error: ' + message;
+    }
+
+    return message;
+}
+
+module.exports = CordovaLogger;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/paramedic.js
----------------------------------------------------------------------
diff --git a/lib/paramedic.js b/lib/paramedic.js
new file mode 100644
index 0000000..3828cf2
--- /dev/null
+++ b/lib/paramedic.js
@@ -0,0 +1,156 @@
+#!/usr/bin/env node
+
+var exec = require('./utils').exec,
+    shell = require('shelljs'),
+    Server = require('./LocalServer'),
+    Q = require('q'),
+    tmp = require('tmp'),
+    PluginsManager = require('./PluginsManager'),
+    specReporters = require('./specReporters'),
+    TestsRunner = require('./TestsRunner'),
+    portScanner = require('./portScanner'),
+    path = require('path'),
+    Tunnel = require('./Tunnel');
+
+var Q = require('q');
+var logger = require('./logger').get();
+
+var TESTS_PASS = 0;
+var TESTS_FAILURE = 1;
+var NONTESTS_FAILURE = 2;
+
+function ParamedicRunner(config, _callback) {
+    this.tunneledUrl = "";
+    this.tempFolder = null;
+    this.pluginsManager = null;
+    this.testsPassed = true;
+
+    this.config = config;
+
+    exec.setVerboseLevel(config.isVerbose());
+    logger.setLevel(config.isVerbose() ? 'verbose' : 'normal');
+}
+
+ParamedicRunner.prototype = {
+    run: function() {
+        var cordovaVersion = exec('cordova --version');
+        var npmVersion = exec('npm -v');
+
+        if (cordovaVersion.code || npmVersion.code) {
+            logger.error(cordovaVersion.output + npmVersion.output);
+            process.exit(1);
+        }
+
+        logger.normal("cordova-paramedic: using cordova version " + cordovaVersion.output.replace('\n', ''));
+        logger.normal("cordova-paramedic: using npm version " + npmVersion.output.replace('\n', ''));
+
+        var self = this;
+
+        this.createTempProject();
+        this.installPlugins();
+
+         // Set up start page for tests
+        logger.normal("cordova-paramedic: setting app start page to test page");
+        shell.sed('-i', 'src="index.html"', 'src="cdvtests/index.html"', 'config.xml');
+
+        var startPort = this.config.getPorts().start,
+            endPort   = this.config.getPorts().end;
+        logger.info("cordova-paramedic: scanning ports from " + startPort + " to " + endPort);
+
+        // Initialize test reporters
+        specReporters.initialize(this.config);
+
+        return portScanner.getFirstAvailablePort(startPort, endPort).then(function(port) {
+            self.port = port;
+
+            logger.info("cordova-paramedic: port " + port + " is available");
+
+            if (self.config.useTunnel()) {
+                self.tunnel = new Tunnel(port);
+                logger.info('cordova-paramedic: attempt to create local tunnel');
+                return self.tunnel.createTunnel();
+            }
+        }).then(function(url) {
+            if (url) {
+                logger.info('cordova-paramedic: using tunneled url ' + url);
+                self.tunneledUrl = url;
+            }
+
+            logger.info("cordova-paramedic: starting local medic server");
+            return Server.startServer(self.port, self.config.getExternalServerUrl(), self.tunneledUrl);
+        }).then(function (server) {
+
+            var testsRunner = new TestsRunner(server);
+            return self.config.getTargets().reduce(function (promise, target) {
+                return promise.then( function() {
+                    return testsRunner.runSingleTarget(target).then(function(results) {
+                        if (results instanceof Error) {
+                            self.testsPassed = false;
+                            return logger.error(results.message);
+                        }
+
+                        logger.info("cordova-paramedic: tests done for platform " + target.platform);
+
+                        var targetTestsPassed = results && results.passed;
+                        self.testsPassed = self.testsPassed && targetTestsPassed;
+
+                        if (!results) {
+                            logger.error("Result: tests has not been completed in time, crashed or there is connectivity issue.");
+                        } else if (targetTestsPassed)  {
+                            logger.info("Result: passed");
+                        } else {
+                            logger.error("Result: passed=" + results.passed + ", failures=" + results.mobilespec.failures);
+                        }
+                    });
+                });
+            }, Q());
+        }).then(function(res) {
+            if (self.testsPassed) {
+                logger.info("All tests have been passed.");
+                return TESTS_PASS;
+            } else {
+                logger.error("There are tests failures.");
+                return TESTS_FAILURE;
+            }
+        }, function(err) {
+            logger.error("Failed: " + err);
+            return NONTESTS_FAILURE;
+        }).then(function(exitCode){
+            if(self.config.shouldCleanUpAfterRun()) {
+                logger.info("cordova-paramedic: Deleting the application: " + self.tempFolder.name);
+                shell.popd();
+                shell.rm('-rf', self.tempFolder.name);
+            }
+            process.exit(exitCode);
+        });
+    },
+    createTempProject: function() {
+        this.tempFolder = tmp.dirSync();
+        tmp.setGracefulCleanup();
+        logger.info("cordova-paramedic: creating temp project at " + this.tempFolder.name);
+        exec('cordova create ' + this.tempFolder.name);
+        shell.pushd(this.tempFolder.name);
+    },
+    installPlugins: function() {
+        logger.info("cordova-paramedic: installing plugins");
+        this.pluginsManager = new PluginsManager(this.tempFolder.name, this.storedCWD);
+        this.pluginsManager.installPlugins(this.config.getPlugins());
+        this.pluginsManager.installTestsForExistingPlugins();
+        this.pluginsManager.installSinglePlugin('cordova-plugin-test-framework');
+        this.pluginsManager.installSinglePlugin('cordova-plugin-device');
+        this.pluginsManager.installSinglePlugin(path.join(__dirname, '../paramedic-plugin'));
+    }
+};
+
+var storedCWD =  null;
+
+exports.run = function(paramedicConfig) {
+
+    storedCWD = storedCWD || process.cwd();
+
+    var runner = new ParamedicRunner(paramedicConfig, null);
+    runner.storedCWD = storedCWD;
+
+    return runner.run()
+    .timeout(paramedicConfig.getTimeout(), "This test seems to be blocked :: timeout exceeded. Exiting ...");
+};

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/portScanner.js
----------------------------------------------------------------------
diff --git a/lib/portScanner.js b/lib/portScanner.js
new file mode 100644
index 0000000..df0cf15
--- /dev/null
+++ b/lib/portScanner.js
@@ -0,0 +1,54 @@
+var net = require('net');
+var Q = require('q');
+var PORT_NOT_AVAILABLE = 'EADDRINUSE';
+
+var isPortAvailable = function (port) {
+    return new Q.Promise(function(resolve, reject) {
+        var testServer = net.createServer()
+            .once('error', function(err) {
+                if (err.code === PORT_NOT_AVAILABLE) {
+                    reject(new Error('Port is not available'));
+                } else {
+                    reject(err);
+                }
+            })
+            .once('listening', function() {
+                testServer.once('close', function() {
+                    resolve(port);
+                }).close();
+            })
+            .listen(port);
+    });
+};
+
+var checkPorts = function(startPort, endPort, onFoundPort, onError) {
+    var currentPort = startPort;
+
+    isPortAvailable(currentPort)
+        .then(function(port) {
+            onFoundPort(port);
+        }, function(error) {
+            if (error.message === 'Port is not available') {
+                    currentPort++;
+                    if (currentPort > endPort) {
+                        onError(new Error('All ports are unavailable!'));
+                    } else {
+                        checkPorts(currentPort, endPort, onFoundPort, onError);
+                    }
+                } else onError(error);
+        });
+    };
+
+var getFirstAvailablePort = function (startPort, endPort) {
+    if (startPort > endPort) {
+        var buffer = startPort;
+        startPort = endPort;
+        endPort = buffer;
+    }
+
+    return new Q.Promise(function(resolve, reject) {
+        checkPorts(startPort, endPort, resolve, reject);
+    });
+};
+
+module.exports.getFirstAvailablePort = getFirstAvailablePort;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/reporters/ConsoleReporter.js
----------------------------------------------------------------------
diff --git a/lib/reporters/ConsoleReporter.js b/lib/reporters/ConsoleReporter.js
new file mode 100644
index 0000000..84f447e
--- /dev/null
+++ b/lib/reporters/ConsoleReporter.js
@@ -0,0 +1,153 @@
+var noopTimer = {
+    start: function(){},
+    elapsed: function(){ return 0; }
+  };
+
+  function ConsoleReporter(options) {
+    var print = options.print,
+      showColors = options.showColors || false,
+      onComplete = options.onComplete || function() {},
+      timer = options.timer || noopTimer,
+      specCount,
+      failureCount,
+      failedSpecs = [],
+      pendingCount,
+      ansi = {
+        green: '\x1B[32m',
+        red: '\x1B[31m',
+        yellow: '\x1B[33m',
+        none: '\x1B[0m'
+      },
+      failedSuites = [];
+
+    this.jasmineStarted = function() {
+      specCount = 0;
+      failureCount = 0;
+      pendingCount = 0;
+      print('Started');
+      printNewline();
+      timer.start();
+    };
+
+    this.jasmineDone = function() {
+      printNewline();
+      for (var i = 0; i < failedSpecs.length; i++) {
+        specFailureDetails(failedSpecs[i]);
+      }
+
+      if(specCount > 0) {
+        printNewline();
+
+        var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' +
+          failureCount + ' ' + plural('failure', failureCount);
+
+        if (pendingCount) {
+          specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount);
+        }
+
+        print(specCounts);
+      } else {
+        print('No specs found');
+      }
+
+      printNewline();
+      var seconds = timer.elapsed() / 1000;
+      print('Finished in ' + seconds + ' ' + plural('second', seconds));
+      printNewline();
+
+      for(i = 0; i < failedSuites.length; i++) {
+        suiteFailureDetails(failedSuites[i]);
+      }
+
+      onComplete(failureCount === 0);
+    };
+
+    this.specStarted = function(){};
+    this.suiteStarted = function(){};
+
+    this.specDone = function(result) {
+      specCount++;
+
+      if (result.status == 'pending') {
+        pendingCount++;
+        print(colored('yellow', '*'));
+        return;
+      }
+
+      if (result.status == 'passed') {
+        print(colored('green', '.'));
+        return;
+      }
+
+      if (result.status == 'failed') {
+        failureCount++;
+        failedSpecs.push(result);
+        print(colored('red', 'F'));
+      }
+    };
+
+    this.suiteDone = function(result) {
+      if (result.failedExpectations && result.failedExpectations.length > 0) {
+        failureCount++;
+        failedSuites.push(result);
+      }
+    };
+
+    return this;
+
+    function printNewline() {
+      print('\n');
+    }
+
+    function colored(color, str) {
+      return showColors ? (ansi[color] + str + ansi.none) : str;
+    }
+
+    function plural(str, count) {
+      return count == 1 ? str : str + 's';
+    }
+
+    function repeat(thing, times) {
+      var arr = [];
+      for (var i = 0; i < times; i++) {
+        arr.push(thing);
+      }
+      return arr;
+    }
+
+    function indent(str, spaces) {
+      var lines = (str || '').split('\n');
+      var newArr = [];
+      for (var i = 0; i < lines.length; i++) {
+        newArr.push(repeat(' ', spaces).join('') + lines[i]);
+      }
+      return newArr.join('\n');
+    }
+
+    function specFailureDetails(result) {
+      printNewline();
+      print(result.fullName);
+
+      for (var i = 0; i < result.failedExpectations.length; i++) {
+        var failedExpectation = result.failedExpectations[i];
+        printNewline();
+        print(indent(failedExpectation.message, 2));
+        print(indent(failedExpectation.stack, 2));
+      }
+
+      printNewline();
+    }
+
+    function suiteFailureDetails(result) {
+      for (var i = 0; i < result.failedExpectations.length; i++) {
+        printNewline();
+        print(colored('red', 'An error was thrown in an afterAll'));
+        printNewline();
+        print(colored('red', 'AfterAll ' + result.failedExpectations[i].message));
+
+      }
+      printNewline();
+    }
+  }
+
+module.exports = ConsoleReporter;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/reporters/ParamedicReporter.js
----------------------------------------------------------------------
diff --git a/lib/reporters/ParamedicReporter.js b/lib/reporters/ParamedicReporter.js
new file mode 100644
index 0000000..2742e67
--- /dev/null
+++ b/lib/reporters/ParamedicReporter.js
@@ -0,0 +1,43 @@
+
+function ParamedicReporter() {
+    var results = [],
+      specsExecuted = 0,
+      failureCount = 0,
+      pendingSpecCount = 0,
+      cordovaInfo = null;
+
+    this.specDone = function(result) {
+        if (result.status != "disabled") {
+            specsExecuted++;
+        }
+        if (result.status == "failed") {
+              failureCount++;
+              results.push(result);
+        }
+        if (result.status == "pending") {
+            pendingSpecCount++;
+        }
+    };
+
+  this.jasmineDone = function(data) {
+      cordovaInfo = data.cordova;
+  };
+
+  this.getResults = function() {
+      return {
+        passed: failureCount === 0,
+            mobilespec: {
+                specs:specsExecuted,
+                failures:failureCount,
+                results: results
+            },
+            platform: cordovaInfo.platform,
+            version: cordovaInfo.version,
+            model: cordovaInfo.model
+        };
+    };
+
+    return this;
+}
+
+module.exports = ParamedicReporter;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/specReporters.js
----------------------------------------------------------------------
diff --git a/lib/specReporters.js b/lib/specReporters.js
new file mode 100644
index 0000000..21e2eab
--- /dev/null
+++ b/lib/specReporters.js
@@ -0,0 +1,68 @@
+
+// not currently used
+// var ConsoleReporter = require('./reporters/ConsoleReporter');
+
+var ParamedicReporter = require('./reporters/ParamedicReporter');
+var JasmineSpecReporter = require('jasmine-spec-reporter');
+var jasmineReporters    = require('jasmine-reporters');
+
+// default reporter which is used to determine whether tests pass or not
+var paramedicReporter = null;
+
+// all reporters including additional reporters to trace results to console, etc
+var reporters = [];
+
+// specifies path to save Junit results file
+var reportSavePath = null;
+
+function reset() {
+    paramedicReporter = new ParamedicReporter();
+    // default paramedic reporter
+    reporters = [paramedicReporter];
+    // extra reporters
+    reporters.push(new JasmineSpecReporter({displayPendingSummary: false, displaySuiteNumber: true}));
+    if(reportSavePath){
+        reporters.push(new jasmineReporters.JUnitXmlReporter({savePath: reportSavePath, consolidateAll: false}));
+    }
+}
+
+module.exports = {
+    reset: reset,
+    initialize: function(config) {
+        reportSavePath = config.getReportSavePath();
+        reset();
+    },
+    getResults: function() {
+        return paramedicReporter.getResults();
+    },
+    jasmineStarted: function(data) {
+        reporters.forEach(function (reporter) {
+            reporter.jasmineStarted && reporter.jasmineStarted(data);
+        });
+    },
+    specStarted: function(data) {
+        reporters.forEach(function (reporter) {
+            reporter.specStarted && reporter.specStarted(data);
+        });
+    },
+    specDone: function(data) {
+        reporters.forEach(function (reporter) {
+            reporter.specDone && reporter.specDone(data);
+        });
+    },
+    suiteStarted: function(data) {
+        reporters.forEach(function (reporter) {
+            reporter.suiteStarted && reporter.suiteStarted(data);
+        });
+    },
+    suiteDone: function(data) {
+        reporters.forEach(function (reporter) {
+            reporter.suiteDone && reporter.suiteDone(data);
+        });
+    },
+    jasmineDone: function(data) {
+        reporters.forEach(function (reporter) {
+            reporter.jasmineDone && reporter.jasmineDone(data);
+        });
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/utils.js
----------------------------------------------------------------------
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..35ce7c7
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,22 @@
+var shelljs = require('shelljs');
+var verbose;
+
+function exec(cmd, onFinish, onData) {
+    if (onFinish instanceof Function || onFinish === null) {
+        var result = shelljs.exec(cmd, {async: true, silent: !verbose}, onFinish);
+
+        if (onData instanceof Function) {
+            result.stdout.on('data', onData);
+        }
+    } else {
+        return shelljs.exec(cmd, {silent: !verbose});
+    }
+}
+
+exec.setVerboseLevel = function(_verbose) {
+    verbose = _verbose;
+};
+
+module.exports = {
+	exec: exec
+};

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/main.js
----------------------------------------------------------------------
diff --git a/main.js b/main.js
index b772665..a538c94 100755
--- a/main.js
+++ b/main.js
@@ -1,43 +1,52 @@
 #!/usr/bin/env node
 
 var parseArgs = require('minimist'),
-    paramedic = require('./paramedic');
-
-var plugins,
-    platformId;
+    path = require('path'),
+    paramedic = require('./lib/paramedic'),
+    ParamedicConfig = require('./lib/ParamedicConfig');
 
 var USAGE = "Error missing args. \n" +
-	"cordova-paramedic --platform PLATFORM --plugin PATH [--justbuild --timeout MSECS --port PORTNUM --browserify]\n" +
-	"`PLATFORM` : the platform id, currently only supports 'ios'\n" +
-	"`PATH` : the relative or absolute path to a plugin folder\n" +
-					"\texpected to have a 'tests' folder.\n" +  
-					"\tYou may specify multiple --plugin flags and they will all\n" + 
-					"\tbe installed and tested together.\n" +
-	"`MSECS` : (optional) time in millisecs to wait for tests to pass|fail \n" +
-			  "\t(defaults to 10 minutes) \n" +
-	"`PORTNUM` : (optional) port to use for posting results from emulator back to paramedic server\n" +
-	"--justbuild : (optional) just builds the project, without running the tests \n" +
+    "cordova-paramedic --platform PLATFORM --plugin PATH [--justbuild --timeout MSECS --startport PORTNUM --endport PORTNUM --browserify]\n" +
+    "`PLATFORM` : the platform id. Currently supports 'ios', 'browser', 'windows', 'android', 'wp8'.\n" +
+                    "\tPath to platform can be specified as link to git repo like:\n" +
+                    "\twindows@https://github.com/apache/cordova-windows.git\n" +
+                    "\tor path to local copied git repo like:\n" +
+                    "\twindows@../cordova-windows/\n" +
+    "`PATH` : the relative or absolute path to a plugin folder\n" +
+                    "\texpected to have a 'tests' folder.\n" +
+                    "\tYou may specify multiple --plugin flags and they will all\n" +
+                    "\tbe installed and tested together.\n" +
+    "`MSECS` : (optional) time in millisecs to wait for tests to pass|fail \n" +
+              "\t(defaults to 10 minutes) \n" +
+    "`PORTNUM` : (optional) ports to find available and use for posting results from emulator back to paramedic server(default is from 8008 to 8009)\n" +
+    "--justbuild : (optional) just builds the project, without running the tests \n" +
     "--browserify : (optional) plugins are browserified into cordova.js \n" +
     "--verbose : (optional) verbose mode. Display more information output\n" +
-    "--platformPath : (optional) path to install platform from, git or local file uri";
+    "--useTunnel : (optional) use tunneling instead of local address. default is false\n" +
+    "--config : (optional) read configuration from paramedic configuration file\n" +
+    "--reportSavePath: (optional) path to save Junit results file\n" +
+    "--cleanUpAfterRun: (optional) cleans up the application after the run.";
 
 var argv = parseArgs(process.argv.slice(2));
+var pathToParamedicConfig = argv.config && path.resolve(argv.config);
 
-if(!argv.platform) {
-    console.log(USAGE);
-    process.exit(1);
-}
+if (pathToParamedicConfig || // --config
+    argv.platform && argv.plugin) { // or --platform and --plugin
 
-var onComplete = function(resCode,resObj,logStr) {
-	console.log("result code is : " + resCode);
-	if(resObj) {
-		console.log(JSON.stringify(resObj));
-	}
-	if(logStr) {
-		console.log(logStr);
-	}
-	process.exit(resCode);
-};
+    var paramedicConfig = pathToParamedicConfig ?
+        ParamedicConfig.parseFromFile(pathToParamedicConfig):
+        ParamedicConfig.parseFromArguments(argv);
 
-paramedic.run(argv.platform, argv.plugin, onComplete, argv.justbuild, argv.port, argv.timeout, argv.browserify, false, argv.verbose, argv.platformPath);
+    paramedic.run(paramedicConfig)
+    .catch(function (error) {
+        console.log(JSON.stringify(error));
+        process.exit(1);
+    })
+    .done(function (result) {
+        console.log(JSON.stringify(result));
+    });
 
+} else {
+    console.log(USAGE);
+    process.exit(1);
+}

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 8eddb4f..77e6ce0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
 {
   "name": "cordova-paramedic",
   "version": "0.5.0",
+  "license": "Apache-2.0",
   "description": "Use medic to test a cordova plugin locally",
   "main": "paramedic.js",
   "bin": {
@@ -11,15 +12,14 @@
     "url": "git://github.com/apache/cordova-paramedic.git"
   },
   "scripts": {
-    "test":"npm run jshint & npm run test-ios",
-    "jshint":"node node_modules/jshint/bin/jshint *.js",
-    "test-appveyor":"npm run test-windows",
-    "test-travis":"npm run test-ios",
-    "test-android":"node main.js --platform android --plugin ./spec/testable-plugin/",
+    "test": "npm run jshint & npm run test-ios",
+    "jshint": "node node_modules/jshint/bin/jshint lib/",
+    "test-appveyor": "npm run test-windows",
+    "test-travis": "npm run test-ios",
+    "test-android": "node main.js --platform android --plugin ./spec/testable-plugin/",
     "test-ios": "node main.js --platform ios --plugin ./spec/testable-plugin/ --verbose",
-    "test-windows" : "node main.js --platform windows --plugin ./spec/testable-plugin/",
+    "test-windows": "node main.js --platform windows --plugin ./spec/testable-plugin/",
     "test-wp8": "node main.js --platform wp8 --plugin ./spec/testable-plugin/"
-
   },
   "keywords": [
     "cordova",
@@ -29,14 +29,20 @@
   "author": "Jesse MacFadyen",
   "license": "Apache 2.0",
   "dependencies": {
+    "ansi": "^0.3.1",
+    "cordova-common": "^1.0.0",
+    "jasmine-spec-reporter": "^2.4.0",
+    "jasmine-reporters": "^2.1.1",
     "localtunnel": "~1.5.0",
     "minimist": "~1.1.0",
+    "q": "^1.4.1",
     "request": "^2.53.0",
     "shelljs": "~0.3.0",
+    "socket.io": "^1.4.5",
     "tmp": "0.0.25"
   },
   "devDependencies": {
-        "jasmine-node": "~1",
-        "jshint": "^2.6.0"
+    "jasmine-node": "~1",
+    "jshint": "^2.6.0"
   }
 }

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/paramedic-plugin/JasmineParamedicProxy.js
----------------------------------------------------------------------
diff --git a/paramedic-plugin/JasmineParamedicProxy.js b/paramedic-plugin/JasmineParamedicProxy.js
new file mode 100644
index 0000000..854efc6
--- /dev/null
+++ b/paramedic-plugin/JasmineParamedicProxy.js
@@ -0,0 +1,56 @@
+function JasmineParamedicProxy(socket) {
+    this.socket = socket;
+    //jasmineRequire.JsApiReporter.apply(this, arguments);
+}
+
+// JasmineParamedicProxy.prototype = jasmineRequire.JsApiReporter.prototype;
+// JasmineParamedicProxy.prototype.constructor = JasmineParamedicProxy;
+
+JasmineParamedicProxy.prototype.jasmineStarted = function (o) {
+    this.socket.emit('jasmineStarted', o);
+};
+
+JasmineParamedicProxy.prototype.specStarted = function (o) {
+    this.socket.emit('specStarted', o);
+};
+
+JasmineParamedicProxy.prototype.specDone = function (o) {
+    this.socket.emit('specDone', o);
+};
+
+JasmineParamedicProxy.prototype.suiteStarted = function (o) {
+    this.socket.emit('suiteStarted', o);
+};
+
+JasmineParamedicProxy.prototype.suiteDone = function (o) {
+    this.socket.emit('suiteDone', o);
+};
+
+JasmineParamedicProxy.prototype.jasmineDone = function (o) {
+    var p = 'Desktop';
+    var devmodel='none';
+    var version = cordova.version;
+    if(typeof device != 'undefined') {
+        p = device.platform.toLowerCase();
+        devmodel=device.model || device.name;
+        version = device.version.toLowerCase();
+    }
+
+    o = o || {};
+
+    // include platform info
+    o.cordova = {
+        platform:(platformMap.hasOwnProperty(p) ? platformMap[p] : p),
+        version:version,
+        model:devmodel
+    }
+
+    this.socket.emit('jasmineDone', o);
+};
+
+var platformMap = {
+    'ipod touch':'ios',
+    'iphone':'ios'
+};
+
+module.exports = JasmineParamedicProxy;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/paramedic-plugin/paramedic.js
----------------------------------------------------------------------
diff --git a/paramedic-plugin/paramedic.js b/paramedic-plugin/paramedic.js
new file mode 100644
index 0000000..6c210e6
--- /dev/null
+++ b/paramedic-plugin/paramedic.js
@@ -0,0 +1,74 @@
+var io = cordova.require('cordova-plugin-paramedic.socket.io');
+
+var PARAMEDIC_SERVER_DEFAULT_URL = 'http://127.0.0.1:8008';
+
+function Paramedic() {
+
+}
+
+Paramedic.prototype.initialize = function() {
+    var me = this;
+    var connectionUri = loadParamedicServerUrl();
+    this.socket = io.connect(connectionUri);
+
+    this.socket.on('connect', function () {
+        console.log("Paramedic has been susccessfully connected to server");
+        if (typeof device != 'undefined') me.socket.emit('deviceInfo', device);
+    });
+
+    this.overrideConsole();
+    this.injectJasmineReporter();
+};
+
+
+Paramedic.prototype.overrideConsole = function () {
+
+    var origConsole = window.console;
+    var me = this;
+
+    function createCustomLogger(type) {
+        return function () {
+            origConsole[type].apply(origConsole, arguments);
+
+            me.socket.emit('log', { type: type, msg: Array.prototype.slice.apply(arguments) });
+        };
+    }
+    window.console = {
+        log: createCustomLogger('log'),
+        warn: createCustomLogger('warn'),
+        error: createCustomLogger('error'),
+    };
+    console.log('Paramedic console has been installed.');
+};
+
+Paramedic.prototype.injectJasmineReporter = function () {
+    var JasmineParamedicProxy = require('cordova-plugin-paramedic.JasmineParamedicProxy');
+    var jasmineProxy = new JasmineParamedicProxy(this.socket);
+    var testsModule = cordova.require("cordova-plugin-test-framework.cdvtests");
+    var defineAutoTestsOriginal = testsModule.defineAutoTests;
+
+    testsModule.defineAutoTests = function () {
+        defineAutoTestsOriginal();
+        jasmine.getEnv().addReporter(jasmineProxy);
+    };
+};
+
+new Paramedic().initialize();
+
+function loadParamedicServerUrl() {
+
+    try {
+        // attempt to synchronously load medic config
+        var xhr = new XMLHttpRequest();
+        xhr.open("GET", "../medic.json", false);
+        xhr.send(null);
+        var cfg = JSON.parse(xhr.responseText);
+
+        return cfg.logurl || PARAMEDIC_SERVER_DEFAULT_URL;
+
+    } catch (ex) {}
+
+    return PARAMEDIC_SERVER_DEFAULT_URL;
+}
+
+module.exports = Paramedic;

http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/paramedic-plugin/plugin.xml
----------------------------------------------------------------------
diff --git a/paramedic-plugin/plugin.xml b/paramedic-plugin/plugin.xml
new file mode 100644
index 0000000..af4d54a
--- /dev/null
+++ b/paramedic-plugin/plugin.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+
+<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
+           id="cordova-plugin-paramedic"
+      version="1.2.1-dev">
+
+    <name>Paramedic</name>
+    <description>Cordova Paramedic Plugin</description>
+    <license>Apache 2.0</license>
+
+    <js-module src="socket.io.js" name="socket.io" />
+
+    <js-module src="JasmineParamedicProxy.js" name="JasmineParamedicProxy" />
+
+    <js-module src="paramedic.js" name="paramedic">
+        <runs/>
+    </js-module>
+
+</plugin>


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org