diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..e11705f7 --- /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": { + } +} diff --git a/.travis.yml b/.travis.yml index 3c024678..2b205dbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ install: - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - npm install cordova - npm install ios-sim +# w/o ios-deploy ios requirements check fails + - npm install ios-deploy - npm install - npm link script: diff --git a/README.md b/README.md index 305feebc..3ad4a902 100644 --- a/README.md +++ b/README.md @@ -7,31 +7,145 @@ 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 +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 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 ``` -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 +####--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. +``` +module.exports = { + // "externalServerUrl": "http://10.0.8.254", + "useTunnel": true, + "plugins": [ + "https://github.com/apache/cordova-plugin-inappbrowser" + ], + "platform": "windows", + "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); ``` diff --git a/appveyor.yml b/appveyor.yml index 8a8cd0fa..e4e7a1c7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,9 +5,6 @@ install: - npm install cordova - npm install - npm link - - cd .. - - git clone https://github.com/apache/cordova-plugin-test-framework - - cd cordova-paramedic build: off diff --git a/lib/LocalServer.js b/lib/LocalServer.js new file mode 100644 index 00000000..0da48ac0 --- /dev/null +++ b/lib/LocalServer.js @@ -0,0 +1,154 @@ +/** + 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 Q = require('q'), + io = require('socket.io'), + logger = require('./utils').logger, + exec = require('./utils').exec, + path = require('path'), + util = require('util'), + portChecker = require('tcp-port-used'), + EventEmitter = require('events').EventEmitter, + localtunnel = require('localtunnel'); + + +// how many ms without a pong packet to consider the connection closed +var CONNECTION_HEARBEAT_PING_TIMEOUT = 60000, +// how many ms before sending a new ping packet + CONNECTION_HEARBEAT_PING_INTERVAL = 25000; + +function LocalServer(port, externalServerUrl) { + this.port = port; + this.externalServerUrl = externalServerUrl; +} + +util.inherits(LocalServer, EventEmitter); + +LocalServer.startServer = function(ports, externalServerUrl, useTunnel) { + logger.normal("local-server: scanning ports from " + ports.start + " to " + ports.end); + + return LocalServer.getFirstAvailablePort(ports.start, ports.end) + .then(function(port) { + logger.normal("local-server: port " + port + " is available"); + logger.info("local-server: starting local medic server"); + + var localServer = new LocalServer(port, externalServerUrl); + localServer.createSocketListener(); + + if (useTunnel) { + return localServer.createTunnel(); + } + + return localServer; + }); +}; + +LocalServer.getFirstAvailablePort = function(startPort, endPort) { + return portChecker.check(startPort).then(function (isInUse) { + if (!isInUse) { + return startPort; + } + if (startPort < endPort) { + return LocalServer.getFirstAvailablePort(startPort + 1, endPort); + } + throw new Error('Unable to find available port'); + }); +}; + +LocalServer.prototype.createTunnel = function() { + logger.info('cordova-paramedic: attempt to create local tunnel'); + var self = this; + + return Q.Promise(function(resolve, reject) { + + var tunnel = localtunnel(self.port, function(err, tunnel) { + if (err) { + reject('Unable to create local tunnel: ' + err); + return; + } + + self.tunneledUrl = tunnel.url; + logger.info('cordova-paramedic: using tunneled url ' + self.tunneledUrl); + + resolve(self); + }); + + // this trace is useful to debug test run timeout issue + tunnel.on('close', function() { + logger.normal('local-server: local tunnel has been closed'); + }); + }); +}; + +LocalServer.prototype.createSocketListener = function() { + var listener = io.listen(this.port, { + pingTimeout: CONNECTION_HEARBEAT_PING_TIMEOUT, + pingInterval: CONNECTION_HEARBEAT_PING_INTERVAL + }); + + var self = this; + + listener.on('connection', function(socket) { + logger.info('local-server: new socket connection'); + self.connection = socket; + + // server methods + ['deviceLog', 'disconnect', 'deviceInfo', + 'jasmineStarted', 'specStarted', 'specDone', + 'suiteStarted', 'suiteDone', 'jasmineDone'].forEach(function(route) { + socket.on(route, function(data) { + self.emit(route, data); + }); + }); + }); +}; + +// Connection url could be platform specific so we pass platform as param here +LocalServer.prototype.getConnectionUrl = function(platformId) { + // --useTunnel option + if (this.tunneledUrl) { + return this.tunneledUrl; + } + // --externalServerUrl option / we know ip or dns name + if (this.externalServerUrl) { + return this.externalServerUrl + ":" + this.port; + } + // build connection uri for localhost based on platform + var connectionUrl; + + switch(platformId) { + case "android" : + connectionUrl = "http://10.0.2.2"; + break; + case "ios" : + case "browser" : + case "windows" : + /* falls through */ + default: + connectionUrl = "http://127.0.0.1"; + } + + return connectionUrl + ":" + this.port; +}; + +LocalServer.prototype.isDeviceConnected = function() { + return !!this.connection; +}; + +module.exports = LocalServer; diff --git a/lib/ParamedicConfig.js b/lib/ParamedicConfig.js new file mode 100644 index 00000000..11b5c0c0 --- /dev/null +++ b/lib/ParamedicConfig.js @@ -0,0 +1,107 @@ +/** + 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 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({ + 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.getUseTunnel = function () { + return this._config.useTunnel; +}; + +ParamedicConfig.prototype.getReportSavePath = function () { + return this._config.reportSavePath; +}; + +ParamedicConfig.prototype.getShouldCleanUpAfterRun = function () { + return this._config.cleanUpAfterRun; +}; + +ParamedicConfig.prototype.getPlatform = function() { + return this._config.platform; +}; + +ParamedicConfig.prototype.getAction = function() { + return this._config.action; +}; + +ParamedicConfig.prototype.getArgs = function() { + return this._config.args; +}; + +ParamedicConfig.prototype.getPlatformId = function() { + return this._config.platform.split("@")[0]; +}; + +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; diff --git a/lib/PluginsManager.js b/lib/PluginsManager.js new file mode 100644 index 00000000..b7d6a17a --- /dev/null +++ b/lib/PluginsManager.js @@ -0,0 +1,71 @@ +/** + 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 path = require('path'); +var fs = require('fs'); +var logger = require('./utils').logger; +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 will list installed plugins and their versions + 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; diff --git a/lib/Reporters.js b/lib/Reporters.js new file mode 100644 index 00000000..9c012c88 --- /dev/null +++ b/lib/Reporters.js @@ -0,0 +1,31 @@ +/** + 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 JasmineSpecReporter = require('jasmine-spec-reporter'), + jasmineReporters = require('jasmine-reporters'); + +module.exports = function(reportSavePath) { + var reporters = [new JasmineSpecReporter({displayPendingSummary: false, displaySuiteNumber: true})]; + + if (reportSavePath) { + reporters.push(new jasmineReporters.JUnitXmlReporter({savePath: reportSavePath, consolidateAll: false})); + } + + return reporters; +}; diff --git a/lib/paramedic.js b/lib/paramedic.js new file mode 100644 index 00000000..be50f90a --- /dev/null +++ b/lib/paramedic.js @@ -0,0 +1,231 @@ +/** + 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 exec = require('./utils').exec, + shell = require('shelljs'), + Server = require('./LocalServer'), + Q = require('q'), + tmp = require('tmp'), + PluginsManager = require('./PluginsManager'), + path = require('path'), + Q = require('q'), + fs = require('fs'), + getReporters = require('./Reporters'), + logger = require('./utils').logger; + +// Time to wait for initial device connection. +// If device has not connected within this interval the tests are stopped. +var INITIAL_CONNECTION_TIMEOUT = 300000; // 5mins + +function ParamedicRunner(config, _callback) { + this.tempFolder = null; + this.pluginsManager = null; + + this.config = config; + + exec.setVerboseLevel(config.isVerbose()); +} + +ParamedicRunner.prototype.run = function() { + var self = this; + + return Q().then(function() { + self.createTempProject(); + self.prepareProjectToRunTests(); + return Server.startServer(self.config.getPorts(), self.config.getExternalServerUrl(), self.config.getUseTunnel()); + }) + .then(function(server) { + self.server = server; + + self.injectReporters(); + self.subcribeForEvents(); + + var connectionUrl = server.getConnectionUrl(self.config.getPlatformId()); + self.writeMedicConnectionUrl(connectionUrl); + + return self.runTests(); + }) + .fin(function() { + self.cleanUpProject(); + }); +}; + +ParamedicRunner.prototype.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); +}; + +ParamedicRunner.prototype.prepareProjectToRunTests = function() { + this.installPlugins(); + this.setUpStartPage(); + this.installPlatform(); + this.checkPlatformRequirements(); +}; + +ParamedicRunner.prototype.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')); +}; + +ParamedicRunner.prototype.setUpStartPage = function() { + logger.normal("cordova-paramedic: setting app start page to test page"); + shell.sed('-i', 'src="index.html"', 'src="cdvtests/index.html"', 'config.xml'); +}; + +ParamedicRunner.prototype.installPlatform = function() { + logger.info("cordova-paramedic: adding platform : " + this.config.getPlatform()); + exec('cordova platform add ' + this.config.getPlatform()); +}; + +ParamedicRunner.prototype.checkPlatformRequirements = function() { + logger.normal("cordova-paramedic: checking requirements for platform " + this.config.getPlatformId()); + var result = exec('cordova requirements ' + this.config.getPlatformId()); + + if (result.code !== 0) + throw new Error('Platform requirements check has failed!'); +}; + +ParamedicRunner.prototype.injectReporters = function() { + var self = this; + var reporters = getReporters(self.config.getReportSavePath()); + + ['jasmineStarted', 'specStarted', 'specDone', + 'suiteStarted', 'suiteDone', 'jasmineDone'].forEach(function(route) { + reporters.forEach(function(reporter) { + if (reporter[route] instanceof Function) + self.server.on(route, reporter[route].bind(reporter)); + }); + }); +}; + +ParamedicRunner.prototype.subcribeForEvents = function() { + this.server.on('deviceLog', function(data) { + logger.verbose('device|console.' + data.type + ': ' + data.msg[0]); + }); + + this.server.on('deviceInfo', function(data) { + logger.normal('cordova-paramedic: Device info: ' + JSON.stringify(data)); + }); +}; + +ParamedicRunner.prototype.writeMedicConnectionUrl = function(url) { + logger.normal("cordova-paramedic: writing medic log url to project " + url); + fs.writeFileSync(path.join("www","medic.json"), JSON.stringify({logurl:url})); +}; + +ParamedicRunner.prototype.runTests = function() { + var self = this; + + return Q.promise(function(resolve, reject) { + self.server.on('jasmineDone', function(data) { + logger.info('cordova-paramedic: tests have been completed'); + + var isTestPassed = (data.specResults.specFailed === 0); + + resolve(isTestPassed); + }); + + self.server.on('disconnect', function() { + reject(new Error('device is disconnected before passing the tests')); + }); + + var command = self.getCommandForStartingTests(); + logger.normal('cordova-paramedic: running command ' + command); + + exec(command, function(code, output) { + if(code) { + // this trace is automatically available in verbose mode + // so we check for this flag to not trace twice + if (!self.config.verbose) { + logger.normal(output); + } + logger.normal('cordova-paramedic: unable to run tests; command log is available above'); + return reject(new Error(command + " returned error code " + code)); + } + + // skip tests if it was just build + if (!self.shouldWaitForTestResult()) { + return resolve(true); + } + + // reject if device not connected in pending time + self.waitForConnection().catch(reject); + }); + }); +}; + +ParamedicRunner.prototype.getCommandForStartingTests = function() { + var cmd = "cordova " + this.config.getAction() + " " + this.config.getPlatformId(); + + if (this.config.getArgs()) { + cmd += " " + this.config.getArgs(); + } + + return cmd; +}; + +ParamedicRunner.prototype.shouldWaitForTestResult = function() { + var action = this.config.getAction(); + return action === 'run' || action === 'emulate'; +}; + +ParamedicRunner.prototype.waitForConnection = function() { + var self = this; + + var ERR_MSG = 'Seems like device not connected to local server in ' + INITIAL_CONNECTION_TIMEOUT / 1000 + ' secs'; + + return Q.promise(function(resolve, reject) { + setTimeout(function() { + if (!self.server.isDeviceConnected()) { + reject(new Error(ERR_MSG)); + } else { + resolve(); + } + }, INITIAL_CONNECTION_TIMEOUT); + }); +}; + +ParamedicRunner.prototype.cleanUpProject = function() { + if(this.config.getShouldCleanUpAfterRun()) { + logger.info("cordova-paramedic: Deleting the application: " + this.tempFolder.name); + shell.popd(); + shell.rm('-rf', this.tempFolder.name); + } +}; + +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 ..."); +}; diff --git a/lib/utils/execWrapper.js b/lib/utils/execWrapper.js new file mode 100644 index 00000000..d1e012d3 --- /dev/null +++ b/lib/utils/execWrapper.js @@ -0,0 +1,39 @@ +/** + 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 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; diff --git a/lib/utils/index.js b/lib/utils/index.js new file mode 100644 index 00000000..38ad5f4d --- /dev/null +++ b/lib/utils/index.js @@ -0,0 +1,23 @@ +/** + 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. +*/ + +module.exports = { + exec: require('./execWrapper'), + logger: require('cordova-common').CordovaLogger.get() +}; diff --git a/main.js b/main.js index b772665d..db49a499 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.error(error.message); + process.exit(1); + }) + .done(function(isTestPassed) { + process.exit(isTestPassed ? 0 : 1); + }); +} else { + console.log(USAGE); + process.exit(1); +} diff --git a/package.json b/package.json index 8eddb4fa..011f5d10 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", @@ -27,16 +27,22 @@ "test" ], "author": "Jesse MacFadyen", - "license": "Apache 2.0", "dependencies": { + "ansi": "^0.3.1", + "cordova-common": "^1.1.0", + "jasmine-reporters": "^2.1.1", + "jasmine-spec-reporter": "^2.4.0", "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", + "tcp-port-used": "^0.1.2", "tmp": "0.0.25" }, "devDependencies": { - "jasmine-node": "~1", - "jshint": "^2.6.0" + "jasmine-node": "~1", + "jshint": "^2.6.0" } } diff --git a/paramedic-plugin/JasmineParamedicProxy.js b/paramedic-plugin/JasmineParamedicProxy.js new file mode 100644 index 00000000..a75135aa --- /dev/null +++ b/paramedic-plugin/JasmineParamedicProxy.js @@ -0,0 +1,86 @@ +/** + 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. +*/ + +function JasmineParamedicProxy(socket) { + this.socket = socket; + this.specExecuted = 0; + this.specFailed = 0; +} + +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) { + if (o.status !== 'disabled') { + this.specExecuted++; + } + if (o.status === 'failed') { + this.specFailed++; + } + + 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 + } + + // include common spec results + o.specResults = { + specExecuted : this.specExecuted, + specFailed : this.specFailed + } + + this.socket.emit('jasmineDone', o); +}; + +var platformMap = { + 'ipod touch':'ios', + 'iphone':'ios' +}; + +module.exports = JasmineParamedicProxy; diff --git a/paramedic-plugin/paramedic.js b/paramedic-plugin/paramedic.js new file mode 100644 index 00000000..d2c6930b --- /dev/null +++ b/paramedic-plugin/paramedic.js @@ -0,0 +1,95 @@ +/** + 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 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('deviceLog', { 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) { + console.log('Unable to load paramedic server url: ' + ex); + } + + return PARAMEDIC_SERVER_DEFAULT_URL; +} + +module.exports = Paramedic; diff --git a/paramedic-plugin/plugin.xml b/paramedic-plugin/plugin.xml new file mode 100644 index 00000000..af4d54ae --- /dev/null +++ b/paramedic-plugin/plugin.xml @@ -0,0 +1,37 @@ + + + + + + Paramedic + Cordova Paramedic Plugin + Apache 2.0 + + + + + + + + + + diff --git a/paramedic-plugin/socket.io.js b/paramedic-plugin/socket.io.js new file mode 100644 index 00000000..6f478d82 --- /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;o0){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,hostname: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=this.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.send([{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;freezeTransport();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&&this.transport.pause){debug("starting upgrade probes");for(var i=0,l=this.upgrades.length;i';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}}inherit(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.prototype.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;idxbytes){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>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>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=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 prevColor=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);logFn.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;i1){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 isBinary=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;i0){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=parseInt(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;i1e4)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;case"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=55296&&value<=56319&&counter65535){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(codePoint)}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=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;var 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;i0&&!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.packetBuffer=[];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("attempting 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;i0&&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};Emitter.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;i1)))/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 parent=(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(object,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=function(value){var result='"',index=0,length=value.length,useCharIndex=!charIndexBuggy||length>10;var symbols=useCharIndex&&(charIndexBuggy?value.split(""):value);for(;index-1/0&&value<1/0){if(getDay){date=floor(value/864e5);for(year=floor(date/365.2425)+1970-1;getDay(year+1,0)<=date;year++);for(month=floor((date-getDay(year,0))/30.42);getDay(year,month+1)<=date;month++);date=1+date-getDay(year,month);time=(value%864e5+864e5)%864e5;hours=floor(time/36e5)%24;minutes=floor(time/6e4)%60;seconds=floor(time/1e3)%60;milliseconds=time%1e3}else{year=value.getUTCFullYear();month=value.getUTCMonth();date=value.getUTCDate();hours=value.getUTCHours();minutes=value.getUTCMinutes();seconds=value.getUTCSeconds();milliseconds=value.getUTCMilliseconds()}value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z"}else{value=null}}else if(typeof value.toJSON=="function"&&(className!=numberClass&&className!=stringClass&&className!=arrayClass||isProperty.call(value,"toJSON"))){value=value.toJSON(property)}}if(callback){value=callback.call(object,property,value)}if(value===null){return"null"}className=getClass.call(value);if(className==booleanClass){return""+value}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null"}else if(className==stringClass){return quote(""+value)}if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError()}}stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index0){for(whitespace="",width>10&&(width=10);whitespace.length=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort()}}value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort()}}else{if(charCode==34){break}charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index)}value+=source.slice(begin,Index)}}if(source.charCodeAt(Index)==34){Index++;return value}abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index)}if(charCode>=48&&charCode<=57){if(charCode==48&&(charCode=source.charCodeAt(Index+1),charCode>=48&&charCode<=57)){abort()}isSigned=false;for(;Index=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++}for(position=Index;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}return+source.slice(begin,Index)}if(isSigned){abort()}if(source.slice(Index,Index+4)=="true"){Index+=4;return true}else if(source.slice(Index,Index+5)=="false"){Index+=5;return false}else if(source.slice(Index,Index+4)=="null"){Index+=4;return null}abort()}}return"$"};var get=function(value){var results,hasMembers;if(value=="$"){abort()}if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1)}if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break}if(hasMembers){if(value==","){value=lex();if(value=="]"){abort()}}else{abort()}}if(value==","){abort()}results.push(get(value))}return results}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break}if(hasMembers){if(value==","){value=lex();if(value=="}"){abort()}}else{abort()}}if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort()}results[value.slice(1)]=get(lex()) +}return results}abort()}return value};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property]}else{source[property]=element}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){if(getClass.call(value)==arrayClass){for(length=value.length;length--;){update(value,length,callback)}}else{forEach(value,function(property){update(value,property,callback)})}}return callback.call(source,property,value)};exports.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort()}Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result}}}exports["runInContext"]=runInContext;return exports}if(freeExports&&!isLoader){runInContext(root,freeExports)}else{var nativeJSON=root.JSON,previousJSON=root["JSON3"],isRestored=false;var JSON3=runInContext(root,root["JSON3"]={noConflict:function(){if(!isRestored){isRestored=true;root.JSON=nativeJSON;root["JSON3"]=previousJSON;nativeJSON=previousJSON=null}return JSON3}});root.JSON={parse:JSON3.parse,stringify:JSON3.stringify}}if(isLoader){define(function(){return JSON3})}}).call(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],51:[function(_dereq_,module,exports){module.exports=toArray;function toArray(list,index){var array=[];index=index||0;for(var i=index||0;i 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"); - } -}; diff --git a/sample-config/.paramedic.config.js b/sample-config/.paramedic.config.js new file mode 100644 index 00000000..6d1bab55 --- /dev/null +++ b/sample-config/.paramedic.config.js @@ -0,0 +1,30 @@ +/** + 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. +*/ + +module.exports = { + //"externalServerUrl": "http://10.0.8.254", + "useTunnel": true, + "verbose": false, + "plugins": [ + "https://github.com/apache/cordova-plugin-inappbrowser" + ], + "platform": "windows", + "action": "run", + "args": "--archs=x64 -- --appx=uap" +};