diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..7de3468 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,284 @@ +{ + "env": { + "browser": true, + "node": true, + "es6": true, + "amd": true + }, + "extends": "eslint:recommended", + "globals" : { + "Paho" : true + }, + "rules": { + "accessor-pairs": "error", + "array-bracket-newline": "off", + "array-bracket-spacing": [ + "error", + "never" + ], + "array-callback-return": "error", + "array-element-newline": "off", + "arrow-body-style": "error", + "arrow-parens": "error", + "arrow-spacing": "error", + "block-scoped-var": "off", + "block-spacing": "off", + "brace-style": "off", + "callback-return": "error", + "camelcase": [ + "error", + { + "properties": "never" + } + ], + "capitalized-comments": "off", + "class-methods-use-this": "error", + "comma-dangle": "off", + "comma-spacing": "off", + "comma-style": [ + "error", + "last" + ], + "complexity": "off", + "computed-property-spacing": [ + "error", + "never" + ], + "consistent-return": "off", + "consistent-this": "error", + "curly": "off", + "default-case": "off", + "dot-location": "error", + "dot-notation": "error", + "eol-last": "error", + "eqeqeq": "off", + "for-direction": "error", + "func-call-spacing": "off", + "func-name-matching": "error", + "func-names": "off", + "func-style": "off", + "function-paren-newline": "off", + "generator-star-spacing": "error", + "getter-return": "error", + "global-require": "error", + "guard-for-in": "off", + "handle-callback-err": "error", + "id-blacklist": "error", + "id-length": "off", + "id-match": "error", + "implicit-arrow-linebreak": "error", + "indent": "off", + "indent-legacy": "off", + "init-declarations": "off", + "jsx-quotes": "error", + "key-spacing": "off", + "keyword-spacing": "off", + "line-comment-position": "off", + "linebreak-style": [ + "error", + "unix" + ], + "lines-around-comment": "off", + "lines-around-directive": "error", + "lines-between-class-members": "error", + "max-depth": "off", + "max-len": "off", + "max-lines": "off", + "max-nested-callbacks": "error", + "max-params": "off", + "max-statements": "off", + "max-statements-per-line": "off", + "multiline-comment-style": "off", + "new-parens": "error", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": "error", + "no-alert": "error", + "no-array-constructor": "error", + "no-await-in-loop": "error", + "no-bitwise": "off", + "no-buffer-constructor": "error", + "no-caller": "error", + "no-catch-shadow": "error", + "no-cond-assign": [ + "error", + "except-parens" + ], + "no-confusing-arrow": "error", + "no-continue": "error", + "no-div-regex": "error", + "no-duplicate-imports": "error", + "no-else-return": "off", + "no-empty-function": "error", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "off", + "no-floating-decimal": "error", + "no-implicit-coercion": "error", + "no-implicit-globals": "off", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-inner-declarations": [ + "error", + "functions" + ], + "no-invalid-this": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "off", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-operators": "off", + "no-mixed-requires": "error", + "no-multi-assign": "off", + "no-multi-spaces": "off", + "no-multi-str": "error", + "no-multiple-empty-lines": "off", + "no-native-reassign": "error", + "no-negated-condition": "off", + "no-negated-in-lhs": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-path-concat": "error", + "no-plusplus": "off", + "no-process-env": "error", + "no-process-exit": "error", + "no-proto": "error", + "no-prototype-builtins": "off", + "no-redeclare" : "warn", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-modules": "error", + "no-restricted-properties": "error", + "no-restricted-syntax": "error", + "no-return-assign": "error", + "no-return-await": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "off", + "no-shadow-restricted-names": "error", + "no-spaced-func": "off", + "no-sync": "error", + "no-tabs": "off", + "no-template-curly-in-string": "error", + "no-ternary": "off", + "no-throw-literal": "error", + "no-trailing-spaces": "off", + "no-undef-init": "error", + "no-undefined": "off", + "no-underscore-dangle": "off", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unused-expressions": "error", + "no-use-before-define": "off", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "off", + "no-useless-constructor": "error", + "no-useless-escape": "warn", + "no-useless-rename": "error", + "no-useless-return": "off", + "no-var": "off", + "no-void": "error", + "no-warning-comments": [ + "error", + { + "location": "start" + } + ], + "no-whitespace-before-property": "error", + "no-with": "error", + "nonblock-statement-body-position": [ + "error", + "any" + ], + "object-curly-newline": "off", + "object-curly-spacing": "off", + "object-property-newline": [ + "error", + { + "allowMultiplePropertiesPerLine": true + } + ], + "object-shorthand": "off", + "one-var": "off", + "one-var-declaration-per-line": [ + "error", + "initializations" + ], + "operator-assignment": "off", + "operator-linebreak": "error", + "padded-blocks": "off", + "padding-line-between-statements": "error", + "prefer-arrow-callback": "off", + "prefer-const": "error", + "prefer-destructuring": "off", + "prefer-numeric-literals": "error", + "prefer-promise-reject-errors": "error", + "prefer-reflect": "off", + "prefer-rest-params": "off", + "prefer-spread": "error", + "prefer-template": "off", + "quote-props": "off", + "quotes": [ + "error", + "double" + ], + "radix": [ + "error", + "as-needed" + ], + "require-await": "error", + "require-jsdoc": "off", + "rest-spread-spacing": "error", + "semi": "error", + "semi-spacing": "off", + "semi-style": [ + "error", + "last" + ], + "sort-imports": "error", + "sort-keys": "off", + "sort-vars": "error", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-in-parens": "off", + "space-infix-ops": "off", + "space-unary-ops": "error", + "spaced-comment": "off", + "strict": [ + "error", + "never" + ], + "switch-colon-spacing": "error", + "symbol-description": "error", + "template-curly-spacing": "error", + "template-tag-spacing": "error", + "unicode-bom": [ + "error", + "never" + ], + "valid-jsdoc": "off", + "valid-typeof": [ + "error", + { + "requireStringLiterals": false + } + ], + "vars-on-top": "off", + "wrap-regex": "error", + "yield-star-spacing": "error" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index f0afc4f..f1138e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ node_modules .npm target +package-lock.json +.env +paho.mqtt.testing/ +persistence/ diff --git a/.travis.yml b/.travis.yml index 84b0181..2a01651 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,8 @@ +language: node_js +node_js: 5 +before_install: + - ./start-broker.sh script: - mvn clean verify + - cp travis.env .env + - mvn clean verify + - ./runTests.sh diff --git a/README.md b/README.md index 1a187b5..4d7a789 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Eclipse Paho JavaScript client +[![Build Status](https://travis-ci.org/eclipse/paho.mqtt.javascript.svg?branch=develop)](https://travis-ci.org/eclipse/paho.mqtt.javascript) + The Paho JavaScript Client is an MQTT browser-based client library written in Javascript that uses WebSockets to connect to an MQTT Broker. ## Project description: @@ -15,7 +17,7 @@ Paho reflects the inherent physical and cost constraints of device connectivity. - GitHub: [https://github.com/eclipse/paho.mqtt.javascript](https://github.com/eclipse/paho.mqtt.javascript) - Twitter: [@eclipsepaho](https://twitter.com/eclipsepaho) - Issues: [github.com/eclipse/paho.mqtt.javascript/issues](https://github.com/eclipse/paho.mqtt.javascript/issues) -- Mailing-list: [https://dev.eclipse.org/mailman/listinfo/paho-dev](https://dev.eclipse.org/mailman/listinfo/paho-dev +- Mailing-list: [https://dev.eclipse.org/mailman/listinfo/paho-dev](https://dev.eclipse.org/mailman/listinfo/paho-dev) ## Using the Paho Javascript Client @@ -73,7 +75,7 @@ This requires the use of a broker that supports WebSockets natively, or the use ```JS // Create a client instance -client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId"); +var client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId"); // set callback handlers client.onConnectionLost = onConnectionLost; @@ -105,3 +107,8 @@ function onMessageArrived(message) { console.log("onMessageArrived:"+message.payloadString); } ``` + +## Breaking Changes + +Previously the Client's Namepsace was `Paho.MQTT`, as of version 1.1.0 (develop branch) this has now been simplified to `Paho`. +You should be able to simply do a find and replace in your code to resolve this, for example all instances of `Paho.MQTT.Client` will now be `Paho.Client` and `Paho.MQTT.Message` will be `Paho.Message`. \ No newline at end of file diff --git a/package.json b/package.json index 30be99d..51ed01c 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,19 @@ { "name": "paho-client", - "description": "Pre reqs for the Paho JS client", - "dependencies": { - "websocket": "1.x.x", - "node-localstorage": "1.3.x", + "description": "Eclipse Paho JavaScript MQTT client", + "version": "1.1.0", + "main": "./src/paho-mqtt.js", + "repository": { + "type": "git", + "url": "https://github.com/eclipse/paho.mqtt.javascript.git" + }, + "license": "EPL-1.0", + "devDependencies": { + "dotenv": "^4.0.0", + "eslint": "^4.12.1", "jasmine-node": "1.14.x", - "jshint" : "2.9.x" + "jsdoc": "^3.5.5", + "node-localstorage": "1.3.x", + "websocket": "1.x.x" } } diff --git a/pom.xml b/pom.xml index b3d9b0f..9b8f9e7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.eclipse.paho paho.javascript pom - ${paho.version} + 1.1.0 http://www.eclipse.org/paho @@ -13,11 +13,10 @@ - 1.0.3 UTF-8 ${maven.build.timestamp} - /home/data/httpd/download.eclipse.org/paho/releases/${paho.version} - ${project.artifactId}-${paho.version} + /home/data/httpd/download.eclipse.org/paho/releases/${project.version} + ${project.artifactId}-${project.version} http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.javascript.git @@ -27,19 +26,6 @@ process-resources - - org.codehaus.mojo - templating-maven-plugin - 1.0-alpha-3 - - - filter-tests - - filter-test-sources - - - - com.samaxes.maven minify-maven-plugin @@ -240,13 +226,13 @@ - jshint - compile + eslint + validate exec - node_modules/jshint/bin/jshint + node_modules/eslint/bin/eslint.js src/paho-mqtt.js diff --git a/runTests.sh b/runTests.sh new file mode 100755 index 0000000..e363c64 --- /dev/null +++ b/runTests.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Temporary Script to run tests whilst the tests only work with the old version of Jasmine +echo "--- BasicTest ---" +./node_modules/jasmine-node/bin/jasmine-node src/test/BasicTest-spec.js --forceexit + +echo "--- base-spec ---" +./node_modules/jasmine-node/bin/jasmine-node src/test/base-spec.js --forceexit + +echo "--- client-uris ---" +./node_modules/jasmine-node/bin/jasmine-node src/test/client-uris-spec.js --forceexit + +echo "--- interops ---" +./node_modules/jasmine-node/bin/jasmine-node src/test/interops-spec.js --forceexit + +echo "--- live-take-over ---" +./node_modules/jasmine-node/bin/jasmine-node src/test/live-take-over-spec.js --forceexit + +echo "--- send-receive ---" +./node_modules/jasmine-node/bin/jasmine-node src/test/send-receive-spec.js --forceexit diff --git a/src/paho-mqtt.js b/src/paho-mqtt.js index b63170b..9ca3d37 100644 --- a/src/paho-mqtt.js +++ b/src/paho-mqtt.js @@ -16,9 +16,9 @@ // Only expose a single object name in the global namespace. -// Everything must go through this module. Global Paho.MQTT module +// Everything must go through this module. Global Paho module // only has a single public function, client, which returns -// a Paho.MQTT client object given connection details. +// a Paho client object given connection details. /** * Send and receive messages using web browsers. @@ -37,12 +37,12 @@ *

* The API consists of two main objects: *

- *
{@link Paho.MQTT.Client}
+ *
{@link Paho.Client}
*
This contains methods that provide the functionality of the API, * including provision of callbacks that notify the application when a message * arrives from or is delivered to the messaging server, * or when the status of its connection to the messaging server changes.
- *
{@link Paho.MQTT.Message}
+ *
{@link Paho.Message}
*
This encapsulates the payload of the message along with various attributes * associated with its delivery, in particular the destination to which it has * been (or is about to be) sent.
@@ -55,7 +55,7 @@ * Example: * *
-client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId");
+var client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId");
 client.onConnectionLost = onConnectionLost;
 client.onMessageArrived = onMessageArrived;
 client.connect({onSuccess:onConnect});
@@ -64,7 +64,7 @@ function onConnect() {
   // Once a connection has been made, make a subscription and send a message.
   console.log("onConnect");
   client.subscribe("/World");
-  message = new Paho.MQTT.Message("Hello");
+  var message = new Paho.MQTT.Message("Hello");
   message.destinationName = "/World";
   client.send(message);
 };
@@ -77,60 +77,71 @@ function onMessageArrived(message) {
   client.disconnect();
 };
  * 
- * @namespace Paho.MQTT + * @namespace Paho */ /* jshint shadow:true */ (function ExportLibrary(root, factory) { - if(typeof exports === 'object' && typeof module === 'object'){ + if(typeof exports === "object" && typeof module === "object"){ module.exports = factory(); - } else if (typeof define === 'function' && define.amd){ + } else if (typeof define === "function" && define.amd){ define(factory); - } else if (typeof exports === 'object'){ + } else if (typeof exports === "object"){ exports = factory(); } else { - if (typeof root.Paho === 'undefined'){ - root.Paho = {}; - } - root.Paho.MQTT = factory(); + //if (typeof root.Paho === "undefined"){ + // root.Paho = {}; + //} + root.Paho = factory(); } })(this, function LibraryFactory(){ -var PahoMQTT = (function (global) { + var PahoMQTT = (function (global) { // Private variables below, these are only visible inside the function closure // which is used to define the module. - - var version = "@VERSION@"; - var buildLevel = "@BUILDLEVEL@"; + var version = "@VERSION@-@BUILDLEVEL@"; /** + * @private + */ + var localStorage = global.localStorage || (function () { + var data = {}; + + return { + setItem: function (key, item) { data[key] = item; }, + getItem: function (key) { return data[key]; }, + removeItem: function (key) { delete data[key]; }, + }; + })(); + + /** * Unique message type identifiers, with associated * associated integer values. * @private */ - var MESSAGE_TYPE = { - CONNECT: 1, - CONNACK: 2, - PUBLISH: 3, - PUBACK: 4, - PUBREC: 5, - PUBREL: 6, - PUBCOMP: 7, - SUBSCRIBE: 8, - SUBACK: 9, - UNSUBSCRIBE: 10, - UNSUBACK: 11, - PINGREQ: 12, - PINGRESP: 13, - DISCONNECT: 14 - }; - - // Collection of utility methods used to simplify module code - // and promote the DRY pattern. + var MESSAGE_TYPE = { + CONNECT: 1, + CONNACK: 2, + PUBLISH: 3, + PUBACK: 4, + PUBREC: 5, + PUBREL: 6, + PUBCOMP: 7, + SUBSCRIBE: 8, + SUBACK: 9, + UNSUBSCRIBE: 10, + UNSUBACK: 11, + PINGREQ: 12, + PINGRESP: 13, + DISCONNECT: 14 + }; - /** + // Collection of utility methods used to simplify module code + // and promote the DRY pattern. + + /** * Validate an object's parameter names to ensure they * match a list of expected variables name for this option * type. Used to ensure option object passed into the API don't @@ -140,24 +151,24 @@ var PahoMQTT = (function (global) { * @throws {Error} Invalid option parameter found. * @private */ - var validate = function(obj, keys) { - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - if (keys.hasOwnProperty(key)) { - if (typeof obj[key] !== keys[key]) - throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key])); - } else { - var errorStr = "Unknown property, " + key + ". Valid properties are:"; - for (var validKey in keys) - if (keys.hasOwnProperty(validKey)) - errorStr = errorStr+" "+validKey; - throw new Error(errorStr); + var validate = function(obj, keys) { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + if (keys.hasOwnProperty(key)) { + if (typeof obj[key] !== keys[key]) + throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key])); + } else { + var errorStr = "Unknown property, " + key + ". Valid properties are:"; + for (var validKey in keys) + if (keys.hasOwnProperty(validKey)) + errorStr = errorStr+" "+validKey; + throw new Error(errorStr); + } } } - } - }; + }; - /** + /** * Return a new function which runs the user function bound * to a fixed scope. * @param {function} User function @@ -165,79 +176,79 @@ var PahoMQTT = (function (global) { * @return {function} User function bound to another scope * @private */ - var scope = function (f, scope) { - return function () { - return f.apply(scope, arguments); + var scope = function (f, scope) { + return function () { + return f.apply(scope, arguments); + }; }; - }; - /** + /** * Unique message type identifiers, with associated * associated integer values. * @private */ - var ERROR = { - OK: {code:0, text:"AMQJSC0000I OK."}, - CONNECT_TIMEOUT: {code:1, text:"AMQJSC0001E Connect timed out."}, - SUBSCRIBE_TIMEOUT: {code:2, text:"AMQJS0002E Subscribe timed out."}, - UNSUBSCRIBE_TIMEOUT: {code:3, text:"AMQJS0003E Unsubscribe timed out."}, - PING_TIMEOUT: {code:4, text:"AMQJS0004E Ping timed out."}, - INTERNAL_ERROR: {code:5, text:"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"}, - CONNACK_RETURNCODE: {code:6, text:"AMQJS0006E Bad Connack return code:{0} {1}."}, - SOCKET_ERROR: {code:7, text:"AMQJS0007E Socket error:{0}."}, - SOCKET_CLOSE: {code:8, text:"AMQJS0008I Socket closed."}, - MALFORMED_UTF: {code:9, text:"AMQJS0009E Malformed UTF data:{0} {1} {2}."}, - UNSUPPORTED: {code:10, text:"AMQJS0010E {0} is not supported by this browser."}, - INVALID_STATE: {code:11, text:"AMQJS0011E Invalid state {0}."}, - INVALID_TYPE: {code:12, text:"AMQJS0012E Invalid type {0} for {1}."}, - INVALID_ARGUMENT: {code:13, text:"AMQJS0013E Invalid argument {0} for {1}."}, - UNSUPPORTED_OPERATION: {code:14, text:"AMQJS0014E Unsupported operation."}, - INVALID_STORED_DATA: {code:15, text:"AMQJS0015E Invalid data in local storage key={0} value={1}."}, - INVALID_MQTT_MESSAGE_TYPE: {code:16, text:"AMQJS0016E Invalid MQTT message type {0}."}, - MALFORMED_UNICODE: {code:17, text:"AMQJS0017E Malformed Unicode string:{0} {1}."}, - BUFFER_FULL: {code:18, text:"AMQJS0018E Message buffer is full, maximum buffer size: {0}."}, - }; - - /** CONNACK RC Meaning. */ - var CONNACK_RC = { - 0:"Connection Accepted", - 1:"Connection Refused: unacceptable protocol version", - 2:"Connection Refused: identifier rejected", - 3:"Connection Refused: server unavailable", - 4:"Connection Refused: bad user name or password", - 5:"Connection Refused: not authorized" - }; + var ERROR = { + OK: {code:0, text:"AMQJSC0000I OK."}, + CONNECT_TIMEOUT: {code:1, text:"AMQJSC0001E Connect timed out."}, + SUBSCRIBE_TIMEOUT: {code:2, text:"AMQJS0002E Subscribe timed out."}, + UNSUBSCRIBE_TIMEOUT: {code:3, text:"AMQJS0003E Unsubscribe timed out."}, + PING_TIMEOUT: {code:4, text:"AMQJS0004E Ping timed out."}, + INTERNAL_ERROR: {code:5, text:"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"}, + CONNACK_RETURNCODE: {code:6, text:"AMQJS0006E Bad Connack return code:{0} {1}."}, + SOCKET_ERROR: {code:7, text:"AMQJS0007E Socket error:{0}."}, + SOCKET_CLOSE: {code:8, text:"AMQJS0008I Socket closed."}, + MALFORMED_UTF: {code:9, text:"AMQJS0009E Malformed UTF data:{0} {1} {2}."}, + UNSUPPORTED: {code:10, text:"AMQJS0010E {0} is not supported by this browser."}, + INVALID_STATE: {code:11, text:"AMQJS0011E Invalid state {0}."}, + INVALID_TYPE: {code:12, text:"AMQJS0012E Invalid type {0} for {1}."}, + INVALID_ARGUMENT: {code:13, text:"AMQJS0013E Invalid argument {0} for {1}."}, + UNSUPPORTED_OPERATION: {code:14, text:"AMQJS0014E Unsupported operation."}, + INVALID_STORED_DATA: {code:15, text:"AMQJS0015E Invalid data in local storage key={0} value={1}."}, + INVALID_MQTT_MESSAGE_TYPE: {code:16, text:"AMQJS0016E Invalid MQTT message type {0}."}, + MALFORMED_UNICODE: {code:17, text:"AMQJS0017E Malformed Unicode string:{0} {1}."}, + BUFFER_FULL: {code:18, text:"AMQJS0018E Message buffer is full, maximum buffer size: {0}."}, + }; + + /** CONNACK RC Meaning. */ + var CONNACK_RC = { + 0:"Connection Accepted", + 1:"Connection Refused: unacceptable protocol version", + 2:"Connection Refused: identifier rejected", + 3:"Connection Refused: server unavailable", + 4:"Connection Refused: bad user name or password", + 5:"Connection Refused: not authorized" + }; /** * Format an error message text. * @private - * @param {error} ERROR.KEY value above. + * @param {error} ERROR value above. * @param {substitutions} [array] substituted into the text. * @return the text with the substitutions made. */ - var format = function(error, substitutions) { - var text = error.text; - if (substitutions) { - var field,start; - for (var i=0; i 0) { - var part1 = text.substring(0,start); - var part2 = text.substring(start+field.length); - text = part1+substitutions[i]+part2; + var format = function(error, substitutions) { + var text = error.text; + if (substitutions) { + var field,start; + for (var i=0; i 0) { + var part1 = text.substring(0,start); + var part2 = text.substring(start+field.length); + text = part1+substitutions[i]+part2; + } + } } - } - } - return text; - }; + return text; + }; - //MQTT protocol and version 6 M Q I s d p 3 - var MqttProtoIdentifierv3 = [0x00,0x06,0x4d,0x51,0x49,0x73,0x64,0x70,0x03]; - //MQTT proto/version for 311 4 M Q T T 4 - var MqttProtoIdentifierv4 = [0x00,0x04,0x4d,0x51,0x54,0x54,0x04]; + //MQTT protocol and version 6 M Q I s d p 3 + var MqttProtoIdentifierv3 = [0x00,0x06,0x4d,0x51,0x49,0x73,0x64,0x70,0x03]; + //MQTT proto/version for 311 4 M Q T T 4 + var MqttProtoIdentifierv4 = [0x00,0x04,0x4d,0x51,0x54,0x54,0x04]; - /** + /** * Construct an MQTT wire protocol message. * @param type MQTT packet type. * @param options optional wire message attributes. @@ -261,43 +272,43 @@ var PahoMQTT = (function (global) { * @private * @ignore */ - var WireMessage = function (type, options) { - this.type = type; - for (var name in options) { - if (options.hasOwnProperty(name)) { - this[name] = options[name]; + var WireMessage = function (type, options) { + this.type = type; + for (var name in options) { + if (options.hasOwnProperty(name)) { + this[name] = options[name]; + } } - } - }; + }; - WireMessage.prototype.encode = function() { + WireMessage.prototype.encode = function() { // Compute the first byte of the fixed header - var first = ((this.type & 0x0f) << 4); + var first = ((this.type & 0x0f) << 4); - /* + /* * Now calculate the length of the variable header + payload by adding up the lengths * of all the component parts */ - var remLength = 0; - var topicStrLength = []; - var destinationNameLength = 0; - var willMessagePayloadBytes; + var remLength = 0; + var topicStrLength = []; + var destinationNameLength = 0; + var willMessagePayloadBytes; - // if the message contains a messageIdentifier then we need two bytes for that - if (this.messageIdentifier !== undefined) - remLength += 2; + // if the message contains a messageIdentifier then we need two bytes for that + if (this.messageIdentifier !== undefined) + remLength += 2; - switch(this.type) { + switch(this.type) { // If this a Connect then we need to include 12 bytes for its header case MESSAGE_TYPE.CONNECT: switch(this.mqttVersion) { - case 3: - remLength += MqttProtoIdentifierv3.length + 3; - break; - case 4: - remLength += MqttProtoIdentifierv4.length + 3; - break; + case 3: + remLength += MqttProtoIdentifierv3.length + 3; + break; + case 4: + remLength += MqttProtoIdentifierv4.length + 3; + break; } remLength += UTF8Length(this.clientId) + 2; @@ -313,7 +324,7 @@ var PahoMQTT = (function (global) { remLength += UTF8Length(this.userName) + 2; if (this.password !== undefined) remLength += UTF8Length(this.password) + 2; - break; + break; // Subscribe, Unsubscribe can both contain topic strings case MESSAGE_TYPE.SUBSCRIBE: @@ -357,26 +368,26 @@ var PahoMQTT = (function (global) { default: break; - } + } - // Now we can allocate a buffer for the message + // Now we can allocate a buffer for the message - var mbi = encodeMBI(remLength); // Convert the length to MQTT MBI format - var pos = mbi.length + 1; // Offset of start of variable header - var buffer = new ArrayBuffer(remLength + pos); - var byteStream = new Uint8Array(buffer); // view it as a sequence of bytes + var mbi = encodeMBI(remLength); // Convert the length to MQTT MBI format + var pos = mbi.length + 1; // Offset of start of variable header + var buffer = new ArrayBuffer(remLength + pos); + var byteStream = new Uint8Array(buffer); // view it as a sequence of bytes - //Write the fixed header into the buffer - byteStream[0] = first; - byteStream.set(mbi,1); + //Write the fixed header into the buffer + byteStream[0] = first; + byteStream.set(mbi,1); - // If this is a PUBLISH then the variable header starts with a topic - if (this.type == MESSAGE_TYPE.PUBLISH) - pos = writeString(this.payloadMessage.destinationName, destinationNameLength, byteStream, pos); - // If this is a CONNECT then the variable header contains the protocol name/version, flags and keepalive time + // If this is a PUBLISH then the variable header starts with a topic + if (this.type == MESSAGE_TYPE.PUBLISH) + pos = writeString(this.payloadMessage.destinationName, destinationNameLength, byteStream, pos); + // If this is a CONNECT then the variable header contains the protocol name/version, flags and keepalive time - else if (this.type == MESSAGE_TYPE.CONNECT) { - switch (this.mqttVersion) { + else if (this.type == MESSAGE_TYPE.CONNECT) { + switch (this.mqttVersion) { case 3: byteStream.set(MqttProtoIdentifierv3, pos); pos += MqttProtoIdentifierv3.length; @@ -385,30 +396,30 @@ var PahoMQTT = (function (global) { byteStream.set(MqttProtoIdentifierv4, pos); pos += MqttProtoIdentifierv4.length; break; - } - var connectFlags = 0; - if (this.cleanSession) - connectFlags = 0x02; - if (this.willMessage !== undefined ) { - connectFlags |= 0x04; - connectFlags |= (this.willMessage.qos<<3); - if (this.willMessage.retained) { - connectFlags |= 0x20; } + var connectFlags = 0; + if (this.cleanSession) + connectFlags = 0x02; + if (this.willMessage !== undefined ) { + connectFlags |= 0x04; + connectFlags |= (this.willMessage.qos<<3); + if (this.willMessage.retained) { + connectFlags |= 0x20; + } + } + if (this.userName !== undefined) + connectFlags |= 0x80; + if (this.password !== undefined) + connectFlags |= 0x40; + byteStream[pos++] = connectFlags; + pos = writeUint16 (this.keepAliveInterval, byteStream, pos); } - if (this.userName !== undefined) - connectFlags |= 0x80; - if (this.password !== undefined) - connectFlags |= 0x40; - byteStream[pos++] = connectFlags; - pos = writeUint16 (this.keepAliveInterval, byteStream, pos); - } - // Output the messageIdentifier - if there is one - if (this.messageIdentifier !== undefined) - pos = writeUint16 (this.messageIdentifier, byteStream, pos); + // Output the messageIdentifier - if there is one + if (this.messageIdentifier !== undefined) + pos = writeUint16 (this.messageIdentifier, byteStream, pos); - switch(this.type) { + switch(this.type) { case MESSAGE_TYPE.CONNECT: pos = writeString(this.clientId, UTF8Length(this.clientId), byteStream, pos); if (this.willMessage !== undefined) { @@ -418,11 +429,11 @@ var PahoMQTT = (function (global) { pos += willMessagePayloadBytes.byteLength; } - if (this.userName !== undefined) - pos = writeString(this.userName, UTF8Length(this.userName), byteStream, pos); - if (this.password !== undefined) - pos = writeString(this.password, UTF8Length(this.password), byteStream, pos); - break; + if (this.userName !== undefined) + pos = writeString(this.userName, UTF8Length(this.userName), byteStream, pos); + if (this.password !== undefined) + pos = writeString(this.password, UTF8Length(this.password), byteStream, pos); + break; case MESSAGE_TYPE.PUBLISH: // PUBLISH has a text or binary payload, if text do not add a 2 byte length field, just the UTF characters. @@ -430,10 +441,10 @@ var PahoMQTT = (function (global) { break; -// case MESSAGE_TYPE.PUBREC: -// case MESSAGE_TYPE.PUBREL: -// case MESSAGE_TYPE.PUBCOMP: -// break; + // case MESSAGE_TYPE.PUBREC: + // case MESSAGE_TYPE.PUBREL: + // case MESSAGE_TYPE.PUBCOMP: + // break; case MESSAGE_TYPE.SUBSCRIBE: // SUBSCRIBE has a list of topic strings and request QoS @@ -451,40 +462,40 @@ var PahoMQTT = (function (global) { default: // Do nothing. - } + } - return buffer; - }; + return buffer; + }; - function decodeMessage(input,pos) { - var startingPos = pos; - var first = input[pos]; - var type = first >> 4; - var messageInfo = first &= 0x0f; - pos += 1; + function decodeMessage(input,pos) { + var startingPos = pos; + var first = input[pos]; + var type = first >> 4; + var messageInfo = first &= 0x0f; + pos += 1; - // Decode the remaining length (MBI format) + // Decode the remaining length (MBI format) - var digit; - var remLength = 0; - var multiplier = 1; - do { - if (pos == input.length) { - return [null,startingPos]; + var digit; + var remLength = 0; + var multiplier = 1; + do { + if (pos == input.length) { + return [null,startingPos]; + } + digit = input[pos++]; + remLength += ((digit & 0x7F) * multiplier); + multiplier *= 128; + } while ((digit & 0x80) !== 0); + + var endPos = pos+remLength; + if (endPos > input.length) { + return [null,startingPos]; } - digit = input[pos++]; - remLength += ((digit & 0x7F) * multiplier); - multiplier *= 128; - } while ((digit & 0x80) !== 0); - - var endPos = pos+remLength; - if (endPos > input.length) { - return [null,startingPos]; - } - var wireMessage = new WireMessage(type); - switch(type) { + var wireMessage = new WireMessage(type); + switch(type) { case MESSAGE_TYPE.CONNACK: var connectAcknowledgeFlags = input[pos++]; if (connectAcknowledgeFlags & 0x01) @@ -505,7 +516,7 @@ var PahoMQTT = (function (global) { pos += 2; } - var message = new Paho.MQTT.Message(input.subarray(pos, endPos)); + var message = new Message(input.subarray(pos, endPos)); if ((messageInfo & 0x01) == 0x01) message.retained = true; if ((messageInfo & 0x08) == 0x08) @@ -531,947 +542,942 @@ var PahoMQTT = (function (global) { default: break; - } + } - return [wireMessage,endPos]; - } + return [wireMessage,endPos]; + } - function writeUint16(input, buffer, offset) { - buffer[offset++] = input >> 8; //MSB - buffer[offset++] = input % 256; //LSB - return offset; - } + function writeUint16(input, buffer, offset) { + buffer[offset++] = input >> 8; //MSB + buffer[offset++] = input % 256; //LSB + return offset; + } - function writeString(input, utf8Length, buffer, offset) { - offset = writeUint16(utf8Length, buffer, offset); - stringToUTF8(input, buffer, offset); - return offset + utf8Length; - } + function writeString(input, utf8Length, buffer, offset) { + offset = writeUint16(utf8Length, buffer, offset); + stringToUTF8(input, buffer, offset); + return offset + utf8Length; + } - function readUint16(buffer, offset) { - return 256*buffer[offset] + buffer[offset+1]; - } + function readUint16(buffer, offset) { + return 256*buffer[offset] + buffer[offset+1]; + } - /** + /** * Encodes an MQTT Multi-Byte Integer * @private */ - function encodeMBI(number) { - var output = new Array(1); - var numBytes = 0; - - do { - var digit = number % 128; - number = number >> 7; - if (number > 0) { - digit |= 0x80; - } - output[numBytes++] = digit; - } while ( (number > 0) && (numBytes<4) ); + function encodeMBI(number) { + var output = new Array(1); + var numBytes = 0; + + do { + var digit = number % 128; + number = number >> 7; + if (number > 0) { + digit |= 0x80; + } + output[numBytes++] = digit; + } while ( (number > 0) && (numBytes<4) ); - return output; - } + return output; + } - /** + /** * Takes a String and calculates its length in bytes when encoded in UTF8. * @private */ - function UTF8Length(input) { - var output = 0; - for (var i = 0; i 0x7FF) - { - // Surrogate pair means its a 4 byte character - if (0xD800 <= charCode && charCode <= 0xDBFF) - { - i++; - output++; - } - output +=3; - } - else if (charCode > 0x7F) - output +=2; - else - output++; + { + // Surrogate pair means its a 4 byte character + if (0xD800 <= charCode && charCode <= 0xDBFF) + { + i++; + output++; + } + output +=3; + } + else if (charCode > 0x7F) + output +=2; + else + output++; + } + return output; } - return output; - } - /** + /** * Takes a String and writes it into an array as UTF8 encoded bytes. * @private */ - function stringToUTF8(input, output, start) { - var pos = start; - for (var i = 0; i>6 & 0x1F | 0xC0; - output[pos++] = charCode & 0x3F | 0x80; - } else if (charCode <= 0xFFFF) { - output[pos++] = charCode>>12 & 0x0F | 0xE0; - output[pos++] = charCode>>6 & 0x3F | 0x80; - output[pos++] = charCode & 0x3F | 0x80; - } else { - output[pos++] = charCode>>18 & 0x07 | 0xF0; - output[pos++] = charCode>>12 & 0x3F | 0x80; - output[pos++] = charCode>>6 & 0x3F | 0x80; - output[pos++] = charCode & 0x3F | 0x80; + if (charCode <= 0x7F) { + output[pos++] = charCode; + } else if (charCode <= 0x7FF) { + output[pos++] = charCode>>6 & 0x1F | 0xC0; + output[pos++] = charCode & 0x3F | 0x80; + } else if (charCode <= 0xFFFF) { + output[pos++] = charCode>>12 & 0x0F | 0xE0; + output[pos++] = charCode>>6 & 0x3F | 0x80; + output[pos++] = charCode & 0x3F | 0x80; + } else { + output[pos++] = charCode>>18 & 0x07 | 0xF0; + output[pos++] = charCode>>12 & 0x3F | 0x80; + output[pos++] = charCode>>6 & 0x3F | 0x80; + output[pos++] = charCode & 0x3F | 0x80; + } } + return output; } - return output; - } - function parseUTF8(input, offset, length) { - var output = ""; - var utf16; - var pos = offset; - - while (pos < offset+length) - { - var byte1 = input[pos++]; - if (byte1 < 128) - utf16 = byte1; - else + function parseUTF8(input, offset, length) { + var output = ""; + var utf16; + var pos = offset; + + while (pos < offset+length) { - var byte2 = input[pos++]-128; - if (byte2 < 0) - throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16),""])); - if (byte1 < 0xE0) // 2 byte character - utf16 = 64*(byte1-0xC0) + byte2; + var byte1 = input[pos++]; + if (byte1 < 128) + utf16 = byte1; else { - var byte3 = input[pos++]-128; - if (byte3 < 0) - throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16)])); - if (byte1 < 0xF0) // 3 byte character - utf16 = 4096*(byte1-0xE0) + 64*byte2 + byte3; - else - { - var byte4 = input[pos++]-128; - if (byte4 < 0) - throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); - if (byte1 < 0xF8) // 4 byte character - utf16 = 262144*(byte1-0xF0) + 4096*byte2 + 64*byte3 + byte4; - else // longer encodings are not supported - throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); - } + var byte2 = input[pos++]-128; + if (byte2 < 0) + throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16),""])); + if (byte1 < 0xE0) // 2 byte character + utf16 = 64*(byte1-0xC0) + byte2; + else + { + var byte3 = input[pos++]-128; + if (byte3 < 0) + throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16)])); + if (byte1 < 0xF0) // 3 byte character + utf16 = 4096*(byte1-0xE0) + 64*byte2 + byte3; + else + { + var byte4 = input[pos++]-128; + if (byte4 < 0) + throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); + if (byte1 < 0xF8) // 4 byte character + utf16 = 262144*(byte1-0xF0) + 4096*byte2 + 64*byte3 + byte4; + else // longer encodings are not supported + throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); + } + } } - } if (utf16 > 0xFFFF) // 4 byte character - express as a surrogate pair - { - utf16 -= 0x10000; - output += String.fromCharCode(0xD800 + (utf16 >> 10)); // lead character - utf16 = 0xDC00 + (utf16 & 0x3FF); // trail character - } - output += String.fromCharCode(utf16); + { + utf16 -= 0x10000; + output += String.fromCharCode(0xD800 + (utf16 >> 10)); // lead character + utf16 = 0xDC00 + (utf16 & 0x3FF); // trail character + } + output += String.fromCharCode(utf16); + } + return output; } - return output; - } - /** + /** * Repeat keepalive requests, monitor responses. * @ignore */ - var Pinger = function(client, window, keepAliveInterval) { - this._client = client; - this._window = window; - this._keepAliveInterval = keepAliveInterval*1000; - this.isReset = false; + var Pinger = function(client, keepAliveInterval) { + this._client = client; + this._keepAliveInterval = keepAliveInterval*1000; + this.isReset = false; - var pingReq = new WireMessage(MESSAGE_TYPE.PINGREQ).encode(); + var pingReq = new WireMessage(MESSAGE_TYPE.PINGREQ).encode(); - var doTimeout = function (pinger) { - return function () { - return doPing.apply(pinger); + var doTimeout = function (pinger) { + return function () { + return doPing.apply(pinger); + }; }; - }; - /** @ignore */ - var doPing = function() { - if (!this.isReset) { - this._client._trace("Pinger.doPing", "Timed out"); - this._client._disconnected( ERROR.PING_TIMEOUT.code , format(ERROR.PING_TIMEOUT)); - } else { - this.isReset = false; - this._client._trace("Pinger.doPing", "send PINGREQ"); - this._client.socket.send(pingReq); - this.timeout = this._window.setTimeout(doTimeout(this), this._keepAliveInterval); - } - }; + /** @ignore */ + var doPing = function() { + if (!this.isReset) { + this._client._trace("Pinger.doPing", "Timed out"); + this._client._disconnected( ERROR.PING_TIMEOUT.code , format(ERROR.PING_TIMEOUT)); + } else { + this.isReset = false; + this._client._trace("Pinger.doPing", "send PINGREQ"); + this._client.socket.send(pingReq); + this.timeout = setTimeout(doTimeout(this), this._keepAliveInterval); + } + }; - this.reset = function() { - this.isReset = true; - this._window.clearTimeout(this.timeout); - if (this._keepAliveInterval > 0) - this.timeout = setTimeout(doTimeout(this), this._keepAliveInterval); - }; + this.reset = function() { + this.isReset = true; + clearTimeout(this.timeout); + if (this._keepAliveInterval > 0) + this.timeout = setTimeout(doTimeout(this), this._keepAliveInterval); + }; - this.cancel = function() { - this._window.clearTimeout(this.timeout); + this.cancel = function() { + clearTimeout(this.timeout); + }; }; - }; - /** + /** * Monitor request completion. * @ignore */ - var Timeout = function(client, window, timeoutSeconds, action, args) { - this._window = window; - if (!timeoutSeconds) - timeoutSeconds = 30; - - var doTimeout = function (action, client, args) { - return function () { - return action.apply(client, args); + var Timeout = function(client, timeoutSeconds, action, args) { + if (!timeoutSeconds) + timeoutSeconds = 30; + + var doTimeout = function (action, client, args) { + return function () { + return action.apply(client, args); + }; }; - }; - this.timeout = setTimeout(doTimeout(action, client, args), timeoutSeconds * 1000); + this.timeout = setTimeout(doTimeout(action, client, args), timeoutSeconds * 1000); - this.cancel = function() { - this._window.clearTimeout(this.timeout); + this.cancel = function() { + clearTimeout(this.timeout); + }; }; - }; - /* + /** * Internal implementation of the Websockets MQTT V3.1 client. * - * @name Paho.MQTT.ClientImpl @constructor + * @name Paho.ClientImpl @constructor * @param {String} host the DNS nameof the webSocket host. * @param {Number} port the port number for that host. * @param {String} clientId the MQ client identifier. */ - var ClientImpl = function (uri, host, port, path, clientId) { + var ClientImpl = function (uri, host, port, path, clientId) { // Check dependencies are satisfied in this browser. - if (!("WebSocket" in global && global.WebSocket !== null)) { - throw new Error(format(ERROR.UNSUPPORTED, ["WebSocket"])); - } - if (!("localStorage" in global && global.localStorage !== null)) { - throw new Error(format(ERROR.UNSUPPORTED, ["localStorage"])); - } - if (!("ArrayBuffer" in global && global.ArrayBuffer !== null)) { - throw new Error(format(ERROR.UNSUPPORTED, ["ArrayBuffer"])); - } - this._trace("Paho.MQTT.Client", uri, host, port, path, clientId); - - this.host = host; - this.port = port; - this.path = path; - this.uri = uri; - this.clientId = clientId; - this._wsuri = null; - - // Local storagekeys are qualified with the following string. - // The conditional inclusion of path in the key is for backward - // compatibility to when the path was not configurable and assumed to - // be /mqtt - this._localKey=host+":"+port+(path!="/mqtt"?":"+path:"")+":"+clientId+":"; - - // Create private instance-only message queue - // Internal queue of messages to be sent, in sending order. - this._msg_queue = []; - this._buffered_msg_queue = []; - - // Messages we have sent and are expecting a response for, indexed by their respective message ids. - this._sentMessages = {}; - - // Messages we have received and acknowleged and are expecting a confirm message for - // indexed by their respective message ids. - this._receivedMessages = {}; - - // Internal list of callbacks to be executed when messages - // have been successfully sent over web socket, e.g. disconnect - // when it doesn't have to wait for ACK, just message is dispatched. - this._notify_msg_sent = {}; - - // Unique identifier for SEND messages, incrementing - // counter as messages are sent. - this._message_identifier = 1; - - // Used to determine the transmission sequence of stored sent messages. - this._sequence = 0; - - - // Load the local state, if any, from the saved version, only restore state relevant to this client. - for (var key in localStorage) - if ( key.indexOf("Sent:"+this._localKey) === 0 || key.indexOf("Received:"+this._localKey) === 0) - this.restore(key); - }; - - // Messaging Client public instance members. - ClientImpl.prototype.host = null; - ClientImpl.prototype.port = null; - ClientImpl.prototype.path = null; - ClientImpl.prototype.uri = null; - ClientImpl.prototype.clientId = null; - - // Messaging Client private instance members. - ClientImpl.prototype.socket = null; - /* true once we have received an acknowledgement to a CONNECT packet. */ - ClientImpl.prototype.connected = false; - /* The largest message identifier allowed, may not be larger than 2**16 but - * if set smaller reduces the maximum number of outbound messages allowed. - */ - ClientImpl.prototype.maxMessageIdentifier = 65536; - ClientImpl.prototype.connectOptions = null; - ClientImpl.prototype.hostIndex = null; - ClientImpl.prototype.onConnected = null; - ClientImpl.prototype.onConnectionLost = null; - ClientImpl.prototype.onMessageDelivered = null; - ClientImpl.prototype.onMessageArrived = null; - ClientImpl.prototype.traceFunction = null; - ClientImpl.prototype._msg_queue = null; - ClientImpl.prototype._buffered_msg_queue = null; - ClientImpl.prototype._connectTimeout = null; - /* The sendPinger monitors how long we allow before we send data to prove to the server that we are alive. */ - ClientImpl.prototype.sendPinger = null; - /* The receivePinger monitors how long we allow before we require evidence that the server is alive. */ - ClientImpl.prototype.receivePinger = null; - ClientImpl.prototype._reconnectInterval = 1; // Reconnect Delay, starts at 1 second - ClientImpl.prototype._reconnecting = false; - ClientImpl.prototype._reconnectTimeout = null; - ClientImpl.prototype.disconnectedPublishing = false; - ClientImpl.prototype.disconnectedBufferSize = 5000; - - ClientImpl.prototype.receiveBuffer = null; - - ClientImpl.prototype._traceBuffer = null; - ClientImpl.prototype._MAX_TRACE_ENTRIES = 100; - - ClientImpl.prototype.connect = function (connectOptions) { - var connectOptionsMasked = this._traceMask(connectOptions, "password"); - this._trace("Client.connect", connectOptionsMasked, this.socket, this.connected); - - if (this.connected) - throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); - if (this.socket) - throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); - - if (this._reconnecting) { + if (!("WebSocket" in global && global.WebSocket !== null)) { + throw new Error(format(ERROR.UNSUPPORTED, ["WebSocket"])); + } + if (!("ArrayBuffer" in global && global.ArrayBuffer !== null)) { + throw new Error(format(ERROR.UNSUPPORTED, ["ArrayBuffer"])); + } + this._trace("Paho.Client", uri, host, port, path, clientId); + + this.host = host; + this.port = port; + this.path = path; + this.uri = uri; + this.clientId = clientId; + this._wsuri = null; + + // Local storagekeys are qualified with the following string. + // The conditional inclusion of path in the key is for backward + // compatibility to when the path was not configurable and assumed to + // be /mqtt + this._localKey=host+":"+port+(path!="/mqtt"?":"+path:"")+":"+clientId+":"; + + // Create private instance-only message queue + // Internal queue of messages to be sent, in sending order. + this._msg_queue = []; + this._buffered_msg_queue = []; + + // Messages we have sent and are expecting a response for, indexed by their respective message ids. + this._sentMessages = {}; + + // Messages we have received and acknowleged and are expecting a confirm message for + // indexed by their respective message ids. + this._receivedMessages = {}; + + // Internal list of callbacks to be executed when messages + // have been successfully sent over web socket, e.g. disconnect + // when it doesn't have to wait for ACK, just message is dispatched. + this._notify_msg_sent = {}; + + // Unique identifier for SEND messages, incrementing + // counter as messages are sent. + this._message_identifier = 1; + + // Used to determine the transmission sequence of stored sent messages. + this._sequence = 0; + + + // Load the local state, if any, from the saved version, only restore state relevant to this client. + for (var key in localStorage) + if ( key.indexOf("Sent:"+this._localKey) === 0 || key.indexOf("Received:"+this._localKey) === 0) + this.restore(key); + }; + + // Messaging Client public instance members. + ClientImpl.prototype.host = null; + ClientImpl.prototype.port = null; + ClientImpl.prototype.path = null; + ClientImpl.prototype.uri = null; + ClientImpl.prototype.clientId = null; + + // Messaging Client private instance members. + ClientImpl.prototype.socket = null; + /* true once we have received an acknowledgement to a CONNECT packet. */ + ClientImpl.prototype.connected = false; + /* The largest message identifier allowed, may not be larger than 2**16 but + * if set smaller reduces the maximum number of outbound messages allowed. + */ + ClientImpl.prototype.maxMessageIdentifier = 65536; + ClientImpl.prototype.connectOptions = null; + ClientImpl.prototype.hostIndex = null; + ClientImpl.prototype.onConnected = null; + ClientImpl.prototype.onConnectionLost = null; + ClientImpl.prototype.onMessageDelivered = null; + ClientImpl.prototype.onMessageArrived = null; + ClientImpl.prototype.traceFunction = null; + ClientImpl.prototype._msg_queue = null; + ClientImpl.prototype._buffered_msg_queue = null; + ClientImpl.prototype._connectTimeout = null; + /* The sendPinger monitors how long we allow before we send data to prove to the server that we are alive. */ + ClientImpl.prototype.sendPinger = null; + /* The receivePinger monitors how long we allow before we require evidence that the server is alive. */ + ClientImpl.prototype.receivePinger = null; + ClientImpl.prototype._reconnectInterval = 1; // Reconnect Delay, starts at 1 second + ClientImpl.prototype._reconnecting = false; + ClientImpl.prototype._reconnectTimeout = null; + ClientImpl.prototype.disconnectedPublishing = false; + ClientImpl.prototype.disconnectedBufferSize = 5000; + + ClientImpl.prototype.receiveBuffer = null; + + ClientImpl.prototype._traceBuffer = null; + ClientImpl.prototype._MAX_TRACE_ENTRIES = 100; + + ClientImpl.prototype.connect = function (connectOptions) { + var connectOptionsMasked = this._traceMask(connectOptions, "password"); + this._trace("Client.connect", connectOptionsMasked, this.socket, this.connected); + + if (this.connected) + throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); + if (this.socket) + throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); + + if (this._reconnecting) { // connect() function is called while reconnect is in progress. // Terminate the auto reconnect process to use new connect options. - this._reconnectTimeout.cancel(); - this._reconnectTimeout = null; - this._reconnecting = false; - } + this._reconnectTimeout.cancel(); + this._reconnectTimeout = null; + this._reconnecting = false; + } - this.connectOptions = connectOptions; - this._reconnectInterval = 1; - this._reconnecting = false; - if (connectOptions.uris) { - this.hostIndex = 0; - this._doConnect(connectOptions.uris[0]); - } else { - this._doConnect(this.uri); - } + this.connectOptions = connectOptions; + this._reconnectInterval = 1; + this._reconnecting = false; + if (connectOptions.uris) { + this.hostIndex = 0; + this._doConnect(connectOptions.uris[0]); + } else { + this._doConnect(this.uri); + } - }; + }; - ClientImpl.prototype.subscribe = function (filter, subscribeOptions) { - this._trace("Client.subscribe", filter, subscribeOptions); + ClientImpl.prototype.subscribe = function (filter, subscribeOptions) { + this._trace("Client.subscribe", filter, subscribeOptions); - if (!this.connected) - throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); + if (!this.connected) + throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); - var wireMessage = new WireMessage(MESSAGE_TYPE.SUBSCRIBE); - wireMessage.topics=[filter]; - if (subscribeOptions.qos !== undefined) - wireMessage.requestedQos = [subscribeOptions.qos]; - else - wireMessage.requestedQos = [0]; + var wireMessage = new WireMessage(MESSAGE_TYPE.SUBSCRIBE); + wireMessage.topics = filter.constructor === Array ? filter : [filter]; + if (subscribeOptions.qos === undefined) + subscribeOptions.qos = 0; + wireMessage.requestedQos = []; + for (var i = 0; i < wireMessage.topics.length; i++) + wireMessage.requestedQos[i] = subscribeOptions.qos; - if (subscribeOptions.onSuccess) { - wireMessage.onSuccess = function(grantedQos) {subscribeOptions.onSuccess({invocationContext:subscribeOptions.invocationContext,grantedQos:grantedQos});}; - } + if (subscribeOptions.onSuccess) { + wireMessage.onSuccess = function(grantedQos) {subscribeOptions.onSuccess({invocationContext:subscribeOptions.invocationContext,grantedQos:grantedQos});}; + } - if (subscribeOptions.onFailure) { - wireMessage.onFailure = function(errorCode) {subscribeOptions.onFailure({invocationContext:subscribeOptions.invocationContext,errorCode:errorCode, errorMessage:format(errorCode)});}; - } + if (subscribeOptions.onFailure) { + wireMessage.onFailure = function(errorCode) {subscribeOptions.onFailure({invocationContext:subscribeOptions.invocationContext,errorCode:errorCode, errorMessage:format(errorCode)});}; + } - if (subscribeOptions.timeout) { - wireMessage.timeOut = new Timeout(this, window, subscribeOptions.timeout, subscribeOptions.onFailure, - [{invocationContext:subscribeOptions.invocationContext, + if (subscribeOptions.timeout) { + wireMessage.timeOut = new Timeout(this, subscribeOptions.timeout, subscribeOptions.onFailure, + [{invocationContext:subscribeOptions.invocationContext, errorCode:ERROR.SUBSCRIBE_TIMEOUT.code, errorMessage:format(ERROR.SUBSCRIBE_TIMEOUT)}]); - } + } - // All subscriptions return a SUBACK. - this._requires_ack(wireMessage); - this._schedule_message(wireMessage); - }; + // All subscriptions return a SUBACK. + this._requires_ack(wireMessage); + this._schedule_message(wireMessage); + }; - /** @ignore */ - ClientImpl.prototype.unsubscribe = function(filter, unsubscribeOptions) { - this._trace("Client.unsubscribe", filter, unsubscribeOptions); + /** @ignore */ + ClientImpl.prototype.unsubscribe = function(filter, unsubscribeOptions) { + this._trace("Client.unsubscribe", filter, unsubscribeOptions); - if (!this.connected) - throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); + if (!this.connected) + throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); - var wireMessage = new WireMessage(MESSAGE_TYPE.UNSUBSCRIBE); - wireMessage.topics = [filter]; + var wireMessage = new WireMessage(MESSAGE_TYPE.UNSUBSCRIBE); + wireMessage.topics = filter.constructor === Array ? filter : [filter]; - if (unsubscribeOptions.onSuccess) { - wireMessage.callback = function() {unsubscribeOptions.onSuccess({invocationContext:unsubscribeOptions.invocationContext});}; - } - if (unsubscribeOptions.timeout) { - wireMessage.timeOut = new Timeout(this, window, unsubscribeOptions.timeout, unsubscribeOptions.onFailure, - [{invocationContext:unsubscribeOptions.invocationContext, + if (unsubscribeOptions.onSuccess) { + wireMessage.callback = function() {unsubscribeOptions.onSuccess({invocationContext:unsubscribeOptions.invocationContext});}; + } + if (unsubscribeOptions.timeout) { + wireMessage.timeOut = new Timeout(this, unsubscribeOptions.timeout, unsubscribeOptions.onFailure, + [{invocationContext:unsubscribeOptions.invocationContext, errorCode:ERROR.UNSUBSCRIBE_TIMEOUT.code, errorMessage:format(ERROR.UNSUBSCRIBE_TIMEOUT)}]); - } + } - // All unsubscribes return a SUBACK. - this._requires_ack(wireMessage); - this._schedule_message(wireMessage); - }; + // All unsubscribes return a SUBACK. + this._requires_ack(wireMessage); + this._schedule_message(wireMessage); + }; - ClientImpl.prototype.send = function (message) { - this._trace("Client.send", message); + ClientImpl.prototype.send = function (message) { + this._trace("Client.send", message); - wireMessage = new WireMessage(MESSAGE_TYPE.PUBLISH); - wireMessage.payloadMessage = message; + var wireMessage = new WireMessage(MESSAGE_TYPE.PUBLISH); + wireMessage.payloadMessage = message; - if (this.connected) { + if (this.connected) { // Mark qos 1 & 2 message as "ACK required" // For qos 0 message, invoke onMessageDelivered callback if there is one. // Then schedule the message. - if (message.qos > 0) { - this._requires_ack(wireMessage); - } else if (this.onMessageDelivered) { - this._notify_msg_sent[wireMessage] = this.onMessageDelivered(wireMessage.payloadMessage); - } - this._schedule_message(wireMessage); - } else { + if (message.qos > 0) { + this._requires_ack(wireMessage); + } else if (this.onMessageDelivered) { + this._notify_msg_sent[wireMessage] = this.onMessageDelivered(wireMessage.payloadMessage); + } + this._schedule_message(wireMessage); + } else { // Currently disconnected, will not schedule this message // Check if reconnecting is in progress and disconnected publish is enabled. - if (this._reconnecting && this.disconnectedPublishing) { + if (this._reconnecting && this.disconnectedPublishing) { // Check the limit which include the "required ACK" messages - var messageCount = Object.keys(this._sentMessages).length + this._buffered_msg_queue.length; - if (messageCount > this.disconnectedBufferSize) { - throw new Error(format(ERROR.BUFFER_FULL, [this.disconnectedBufferSize])); - } else { - if (message.qos > 0) { - // Mark this message as "ACK required" - this._requires_ack(wireMessage); + var messageCount = Object.keys(this._sentMessages).length + this._buffered_msg_queue.length; + if (messageCount > this.disconnectedBufferSize) { + throw new Error(format(ERROR.BUFFER_FULL, [this.disconnectedBufferSize])); } else { - wireMessage.sequence = ++this._sequence; - this._buffered_msg_queue.push(wireMessage); + if (message.qos > 0) { + // Mark this message as "ACK required" + this._requires_ack(wireMessage); + } else { + wireMessage.sequence = ++this._sequence; + // Add messages in fifo order to array, by adding to start + this._buffered_msg_queue.unshift(wireMessage); + } } + } else { + throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); } - } else { - throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); } - } - }; + }; - ClientImpl.prototype.disconnect = function () { - this._trace("Client.disconnect"); + ClientImpl.prototype.disconnect = function () { + this._trace("Client.disconnect"); - if (this._reconnecting) { + if (this._reconnecting) { // disconnect() function is called while reconnect is in progress. // Terminate the auto reconnect process. - this._reconnectTimeout.cancel(); - this._reconnectTimeout = null; - this._reconnecting = false; - } + this._reconnectTimeout.cancel(); + this._reconnectTimeout = null; + this._reconnecting = false; + } - if (!this.socket) - throw new Error(format(ERROR.INVALID_STATE, ["not connecting or connected"])); + if (!this.socket) + throw new Error(format(ERROR.INVALID_STATE, ["not connecting or connected"])); - wireMessage = new WireMessage(MESSAGE_TYPE.DISCONNECT); + var wireMessage = new WireMessage(MESSAGE_TYPE.DISCONNECT); - // Run the disconnected call back as soon as the message has been sent, - // in case of a failure later on in the disconnect processing. - // as a consequence, the _disconected call back may be run several times. - this._notify_msg_sent[wireMessage] = scope(this._disconnected, this); + // Run the disconnected call back as soon as the message has been sent, + // in case of a failure later on in the disconnect processing. + // as a consequence, the _disconected call back may be run several times. + this._notify_msg_sent[wireMessage] = scope(this._disconnected, this); - this._schedule_message(wireMessage); - }; + this._schedule_message(wireMessage); + }; - ClientImpl.prototype.getTraceLog = function () { - if ( this._traceBuffer !== null ) { - this._trace("Client.getTraceLog", new Date()); - this._trace("Client.getTraceLog in flight messages", this._sentMessages.length); - for (var key in this._sentMessages) - this._trace("_sentMessages ",key, this._sentMessages[key]); - for (var key in this._receivedMessages) - this._trace("_receivedMessages ",key, this._receivedMessages[key]); + ClientImpl.prototype.getTraceLog = function () { + if ( this._traceBuffer !== null ) { + this._trace("Client.getTraceLog", new Date()); + this._trace("Client.getTraceLog in flight messages", this._sentMessages.length); + for (var key in this._sentMessages) + this._trace("_sentMessages ",key, this._sentMessages[key]); + for (var key in this._receivedMessages) + this._trace("_receivedMessages ",key, this._receivedMessages[key]); - return this._traceBuffer; - } - }; + return this._traceBuffer; + } + }; - ClientImpl.prototype.startTrace = function () { - if ( this._traceBuffer === null ) { - this._traceBuffer = []; - } - this._trace("Client.startTrace", new Date(), version); - }; + ClientImpl.prototype.startTrace = function () { + if ( this._traceBuffer === null ) { + this._traceBuffer = []; + } + this._trace("Client.startTrace", new Date(), version); + }; - ClientImpl.prototype.stopTrace = function () { - delete this._traceBuffer; - }; + ClientImpl.prototype.stopTrace = function () { + delete this._traceBuffer; + }; - ClientImpl.prototype._doConnect = function (wsurl) { + ClientImpl.prototype._doConnect = function (wsurl) { // When the socket is open, this client will send the CONNECT WireMessage using the saved parameters. - if (this.connectOptions.useSSL) { - var uriParts = wsurl.split(":"); - uriParts[0] = "wss"; - wsurl = uriParts.join(":"); - } - this._wsuri = wsurl; - this.connected = false; + if (this.connectOptions.useSSL) { + var uriParts = wsurl.split(":"); + uriParts[0] = "wss"; + wsurl = uriParts.join(":"); + } + this._wsuri = wsurl; + this.connected = false; - if (this.connectOptions.mqttVersion < 4) { - this.socket = new WebSocket(wsurl, ["mqttv3.1"]); - } else { - this.socket = new WebSocket(wsurl, ["mqtt"]); - } - this.socket.binaryType = 'arraybuffer'; - this.socket.onopen = scope(this._on_socket_open, this); - this.socket.onmessage = scope(this._on_socket_message, this); - this.socket.onerror = scope(this._on_socket_error, this); - this.socket.onclose = scope(this._on_socket_close, this); - - this.sendPinger = new Pinger(this, window, this.connectOptions.keepAliveInterval); - this.receivePinger = new Pinger(this, window, this.connectOptions.keepAliveInterval); - if (this._connectTimeout) { - this._connectTimeout.cancel(); - this._connectTimeout = null; - } - this._connectTimeout = new Timeout(this, window, this.connectOptions.timeout, this._disconnected, [ERROR.CONNECT_TIMEOUT.code, format(ERROR.CONNECT_TIMEOUT)]); - }; - - - // Schedule a new message to be sent over the WebSockets - // connection. CONNECT messages cause WebSocket connection - // to be started. All other messages are queued internally - // until this has happened. When WS connection starts, process - // all outstanding messages. - ClientImpl.prototype._schedule_message = function (message) { - this._msg_queue.push(message); - // Process outstanding messages in the queue if we have an open socket, and have received CONNACK. - if (this.connected) { - this._process_queue(); - } - }; - - ClientImpl.prototype.store = function(prefix, wireMessage) { - var storedMessage = {type:wireMessage.type, messageIdentifier:wireMessage.messageIdentifier, version:1}; - - switch(wireMessage.type) { - case MESSAGE_TYPE.PUBLISH: - if(wireMessage.pubRecReceived) - storedMessage.pubRecReceived = true; - - // Convert the payload to a hex string. - storedMessage.payloadMessage = {}; - var hex = ""; - var messageBytes = wireMessage.payloadMessage.payloadBytes; - for (var i=0; i= 2) { - var x = parseInt(hex.substring(0, 2), 16); - hex = hex.substring(2, hex.length); - byteStream[i++] = x; - } - var payloadMessage = new Paho.MQTT.Message(byteStream); - - payloadMessage.qos = storedMessage.payloadMessage.qos; - payloadMessage.destinationName = storedMessage.payloadMessage.destinationName; - if (storedMessage.payloadMessage.duplicate) - payloadMessage.duplicate = true; - if (storedMessage.payloadMessage.retained) - payloadMessage.retained = true; - wireMessage.payloadMessage = payloadMessage; - - break; + throw Error(format(ERROR.INVALID_STORED_DATA, [prefix+this._localKey+wireMessage.messageIdentifier, storedMessage])); + } + localStorage.setItem(prefix+this._localKey+wireMessage.messageIdentifier, JSON.stringify(storedMessage)); + }; + + ClientImpl.prototype.restore = function(key) { + var value = localStorage.getItem(key); + var storedMessage = JSON.parse(value); + + var wireMessage = new WireMessage(storedMessage.type, storedMessage); + + switch(storedMessage.type) { + case MESSAGE_TYPE.PUBLISH: + // Replace the payload message with a Message object. + var hex = storedMessage.payloadMessage.payloadHex; + var buffer = new ArrayBuffer((hex.length)/2); + var byteStream = new Uint8Array(buffer); + var i = 0; + while (hex.length >= 2) { + var x = parseInt(hex.substring(0, 2), 16); + hex = hex.substring(2, hex.length); + byteStream[i++] = x; + } + var payloadMessage = new Message(byteStream); + + payloadMessage.qos = storedMessage.payloadMessage.qos; + payloadMessage.destinationName = storedMessage.payloadMessage.destinationName; + if (storedMessage.payloadMessage.duplicate) + payloadMessage.duplicate = true; + if (storedMessage.payloadMessage.retained) + payloadMessage.retained = true; + wireMessage.payloadMessage = payloadMessage; + + break; default: - throw Error(format(ERROR.INVALID_STORED_DATA, [key, value])); - } + throw Error(format(ERROR.INVALID_STORED_DATA, [key, value])); + } - if (key.indexOf("Sent:"+this._localKey) === 0) { - wireMessage.payloadMessage.duplicate = true; - this._sentMessages[wireMessage.messageIdentifier] = wireMessage; - } else if (key.indexOf("Received:"+this._localKey) === 0) { - this._receivedMessages[wireMessage.messageIdentifier] = wireMessage; - } - }; - - ClientImpl.prototype._process_queue = function () { - var message = null; - // Process messages in order they were added - var fifo = this._msg_queue.reverse(); - - // Send all queued messages down socket connection - while ((message = fifo.pop())) { - this._socket_send(message); - // Notify listeners that message was successfully sent - if (this._notify_msg_sent[message]) { - this._notify_msg_sent[message](); - delete this._notify_msg_sent[message]; + if (key.indexOf("Sent:"+this._localKey) === 0) { + wireMessage.payloadMessage.duplicate = true; + this._sentMessages[wireMessage.messageIdentifier] = wireMessage; + } else if (key.indexOf("Received:"+this._localKey) === 0) { + this._receivedMessages[wireMessage.messageIdentifier] = wireMessage; } - } - }; + }; - /** + ClientImpl.prototype._process_queue = function () { + var message = null; + + // Send all queued messages down socket connection + while ((message = this._msg_queue.pop())) { + this._socket_send(message); + // Notify listeners that message was successfully sent + if (this._notify_msg_sent[message]) { + this._notify_msg_sent[message](); + delete this._notify_msg_sent[message]; + } + } + }; + + /** * Expect an ACK response for this message. Add message to the set of in progress * messages and set an unused identifier in this message. * @ignore */ - ClientImpl.prototype._requires_ack = function (wireMessage) { - var messageCount = Object.keys(this._sentMessages).length; - if (messageCount > this.maxMessageIdentifier) - throw Error ("Too many messages:"+messageCount); + ClientImpl.prototype._requires_ack = function (wireMessage) { + var messageCount = Object.keys(this._sentMessages).length; + if (messageCount > this.maxMessageIdentifier) + throw Error ("Too many messages:"+messageCount); - while(this._sentMessages[this._message_identifier] !== undefined) { - this._message_identifier++; - } - wireMessage.messageIdentifier = this._message_identifier; - this._sentMessages[wireMessage.messageIdentifier] = wireMessage; - if (wireMessage.type === MESSAGE_TYPE.PUBLISH) { - this.store("Sent:", wireMessage); - } - if (this._message_identifier === this.maxMessageIdentifier) { - this._message_identifier = 1; - } - }; + while(this._sentMessages[this._message_identifier] !== undefined) { + this._message_identifier++; + } + wireMessage.messageIdentifier = this._message_identifier; + this._sentMessages[wireMessage.messageIdentifier] = wireMessage; + if (wireMessage.type === MESSAGE_TYPE.PUBLISH) { + this.store("Sent:", wireMessage); + } + if (this._message_identifier === this.maxMessageIdentifier) { + this._message_identifier = 1; + } + }; - /** + /** * Called when the underlying websocket has been opened. * @ignore */ - ClientImpl.prototype._on_socket_open = function () { + ClientImpl.prototype._on_socket_open = function () { // Create the CONNECT message object. - var wireMessage = new WireMessage(MESSAGE_TYPE.CONNECT, this.connectOptions); - wireMessage.clientId = this.clientId; - this._socket_send(wireMessage); - }; + var wireMessage = new WireMessage(MESSAGE_TYPE.CONNECT, this.connectOptions); + wireMessage.clientId = this.clientId; + this._socket_send(wireMessage); + }; - /** + /** * Called when the underlying websocket has received a complete packet. * @ignore */ - ClientImpl.prototype._on_socket_message = function (event) { - this._trace("Client._on_socket_message", event.data); - var messages = this._deframeMessages(event.data); - for (var i = 0; i < messages.length; i+=1) { - this._handleMessage(messages[i]); - } - }; - - ClientImpl.prototype._deframeMessages = function(data) { - var byteArray = new Uint8Array(data); - var messages = []; - if (this.receiveBuffer) { - var newData = new Uint8Array(this.receiveBuffer.length+byteArray.length); - newData.set(this.receiveBuffer); - newData.set(byteArray,this.receiveBuffer.length); - byteArray = newData; - delete this.receiveBuffer; - } - try { - var offset = 0; - while(offset < byteArray.length) { - var result = decodeMessage(byteArray,offset); - var wireMessage = result[0]; - offset = result[1]; - if (wireMessage !== null) { - messages.push(wireMessage); - } else { - break; - } - } - if (offset < byteArray.length) { - this.receiveBuffer = byteArray.subarray(offset); - } - } catch (error) { - var errorStack = ((error.hasOwnProperty('stack') == 'undefined') ? error.stack.toString() : "No Error Stack Available"); - this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,errorStack])); - return; - } - return messages; - }; + ClientImpl.prototype._on_socket_message = function (event) { + this._trace("Client._on_socket_message", event.data); + var messages = this._deframeMessages(event.data); + for (var i = 0; i < messages.length; i+=1) { + this._handleMessage(messages[i]); + } + }; - ClientImpl.prototype._handleMessage = function(wireMessage) { + ClientImpl.prototype._deframeMessages = function(data) { + var byteArray = new Uint8Array(data); + var messages = []; + if (this.receiveBuffer) { + var newData = new Uint8Array(this.receiveBuffer.length+byteArray.length); + newData.set(this.receiveBuffer); + newData.set(byteArray,this.receiveBuffer.length); + byteArray = newData; + delete this.receiveBuffer; + } + try { + var offset = 0; + while(offset < byteArray.length) { + var result = decodeMessage(byteArray,offset); + var wireMessage = result[0]; + offset = result[1]; + if (wireMessage !== null) { + messages.push(wireMessage); + } else { + break; + } + } + if (offset < byteArray.length) { + this.receiveBuffer = byteArray.subarray(offset); + } + } catch (error) { + var errorStack = ((error.hasOwnProperty("stack") == "undefined") ? error.stack.toString() : "No Error Stack Available"); + this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,errorStack])); + return; + } + return messages; + }; - this._trace("Client._handleMessage", wireMessage); + ClientImpl.prototype._handleMessage = function(wireMessage) { - try { - switch(wireMessage.type) { - case MESSAGE_TYPE.CONNACK: - this._connectTimeout.cancel(); - if (this._reconnectTimeout) - this._reconnectTimeout.cancel(); - - // If we have started using clean session then clear up the local state. - if (this.connectOptions.cleanSession) { - for (var key in this._sentMessages) { - var sentMessage = this._sentMessages[key]; - localStorage.removeItem("Sent:"+this._localKey+sentMessage.messageIdentifier); + this._trace("Client._handleMessage", wireMessage); + + try { + switch(wireMessage.type) { + case MESSAGE_TYPE.CONNACK: + this._connectTimeout.cancel(); + if (this._reconnectTimeout) + this._reconnectTimeout.cancel(); + + // If we have started using clean session then clear up the local state. + if (this.connectOptions.cleanSession) { + for (var key in this._sentMessages) { + var sentMessage = this._sentMessages[key]; + localStorage.removeItem("Sent:"+this._localKey+sentMessage.messageIdentifier); + } + this._sentMessages = {}; + + for (var key in this._receivedMessages) { + var receivedMessage = this._receivedMessages[key]; + localStorage.removeItem("Received:"+this._localKey+receivedMessage.messageIdentifier); + } + this._receivedMessages = {}; + } + // Client connected and ready for business. + if (wireMessage.returnCode === 0) { + + this.connected = true; + // Jump to the end of the list of uris and stop looking for a good host. + + if (this.connectOptions.uris) + this.hostIndex = this.connectOptions.uris.length; + + } else { + this._disconnected(ERROR.CONNACK_RETURNCODE.code , format(ERROR.CONNACK_RETURNCODE, [wireMessage.returnCode, CONNACK_RC[wireMessage.returnCode]])); + break; + } + + // Resend messages. + var sequencedMessages = []; + for (var msgId in this._sentMessages) { + if (this._sentMessages.hasOwnProperty(msgId)) + sequencedMessages.push(this._sentMessages[msgId]); + } + + // Also schedule qos 0 buffered messages if any + if (this._buffered_msg_queue.length > 0) { + var msg = null; + while ((msg = this._buffered_msg_queue.pop())) { + sequencedMessages.push(msg); + if (this.onMessageDelivered) + this._notify_msg_sent[msg] = this.onMessageDelivered(msg.payloadMessage); + } } - this._sentMessages = {}; - for (var key in this._receivedMessages) { - var receivedMessage = this._receivedMessages[key]; - localStorage.removeItem("Received:"+this._localKey+receivedMessage.messageIdentifier); + // Sort sentMessages into the original sent order. + var sequencedMessages = sequencedMessages.sort(function(a,b) {return a.sequence - b.sequence;} ); + for (var i=0, len=sequencedMessages.length; i 0) { - var msg = null; - var fifo = this._buffered_msg_queue.reverse(); - while ((msg = fifo.pop())) { - sequencedMessages.push(msg); + case MESSAGE_TYPE.PUBACK: + var sentMessage = this._sentMessages[wireMessage.messageIdentifier]; + // If this is a re flow of a PUBACK after we have restarted receivedMessage will not exist. + if (sentMessage) { + delete this._sentMessages[wireMessage.messageIdentifier]; + localStorage.removeItem("Sent:"+this._localKey+wireMessage.messageIdentifier); if (this.onMessageDelivered) - this._notify_msg_sent[msg] = this.onMessageDelivered(msg.payloadMessage); + this.onMessageDelivered(sentMessage.payloadMessage); } - } + break; - // Sort sentMessages into the original sent order. - var sequencedMessages = sequencedMessages.sort(function(a,b) {return a.sequence - b.sequence;} ); - for (var i=0, len=sequencedMessages.length; i * Most applications will create just one Client object and then call its connect() method, * however applications can create more than one Client object if they wish. @@ -1676,10 +1683,10 @@ var PahoMQTT = (function (global) { * of the script that made the invocation. *

* In contrast there are some callback functions, most notably onMessageArrived, - * that are defined on the {@link Paho.MQTT.Client} object. + * that are defined on the {@link Paho.Client} object. * These may get called multiple times, and aren't directly related to specific method invocations made by the client. * - * @name Paho.MQTT.Client + * @name Paho.Client * * @constructor * @@ -1710,12 +1717,12 @@ var PahoMQTT = (function (global) { * and the message has been removed from persistent storage before this callback is invoked. * Parameters passed to the onMessageDelivered callback are: *

    - *
  1. {@link Paho.MQTT.Message} that was delivered. + *
  2. {@link Paho.Message} that was delivered. *
- * @property {function} onMessageArrived - called when a message has arrived in this Paho.MQTT.client. + * @property {function} onMessageArrived - called when a message has arrived in this Paho.client. * Parameters passed to the onMessageArrived callback are: *
    - *
  1. {@link Paho.MQTT.Message} that has arrived. + *
  2. {@link Paho.Message} that has arrived. *
* @property {function} onConnected - called when a connection is successfully made to the server. * after a connect() method. @@ -1730,122 +1737,139 @@ var PahoMQTT = (function (global) { * buffer will hold before rejecting new messages. Default size: 5000 messages * @property {function} trace - called whenever trace is called. TODO */ - var Client = function (host, port, path, clientId) { - - var uri; - - if (typeof host !== "string") - throw new Error(format(ERROR.INVALID_TYPE, [typeof host, "host"])); - - if (arguments.length == 2) { - // host: must be full ws:// uri - // port: clientId - clientId = port; - uri = host; - var match = uri.match(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/); - if (match) { - host = match[4]||match[2]; - port = parseInt(match[7]); - path = match[8]; - } else { - throw new Error(format(ERROR.INVALID_ARGUMENT,[host,"host"])); - } - } else { - if (arguments.length == 3) { - clientId = path; - path = "/mqtt"; - } - if (typeof port !== "number" || port < 0) - throw new Error(format(ERROR.INVALID_TYPE, [typeof port, "port"])); - if (typeof path !== "string") - throw new Error(format(ERROR.INVALID_TYPE, [typeof path, "path"])); - - var ipv6AddSBracket = (host.indexOf(":") !== -1 && host.slice(0,1) !== "[" && host.slice(-1) !== "]"); - uri = "ws://"+(ipv6AddSBracket?"["+host+"]":host)+":"+port+path; - } + var Client = function (host, port, path, clientId) { + + var uri; + + if (typeof host !== "string") + throw new Error(format(ERROR.INVALID_TYPE, [typeof host, "host"])); + + if (arguments.length == 2) { + // host: must be full ws:// uri + // port: clientId + clientId = port; + uri = host; + var match = uri.match(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/); + if (match) { + host = match[4]||match[2]; + port = parseInt(match[7]); + path = match[8]; + } else { + throw new Error(format(ERROR.INVALID_ARGUMENT,[host,"host"])); + } + } else { + if (arguments.length == 3) { + clientId = path; + path = "/mqtt"; + } + if (typeof port !== "number" || port < 0) + throw new Error(format(ERROR.INVALID_TYPE, [typeof port, "port"])); + if (typeof path !== "string") + throw new Error(format(ERROR.INVALID_TYPE, [typeof path, "path"])); - var clientIdLength = 0; - for (var i = 0; i 65535) - throw new Error(format(ERROR.INVALID_ARGUMENT, [clientId, "clientId"])); - - var client = new ClientImpl(uri, host, port, path, clientId); - this._getHost = function() { return host; }; - this._setHost = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - - this._getPort = function() { return port; }; - this._setPort = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - this._getPath = function() { return path; }; - this._setPath = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - - this._getURI = function() { return uri; }; - this._setURI = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - - this._getClientId = function() { return client.clientId; }; - this._setClientId = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; - - this._getOnConnected = function() { return client.onConnected; }; - this._setOnConnected = function(newOnConnected) { - if (typeof newOnConnected === "function") - client.onConnected = newOnConnected; - else - throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnected, "onConnected"])); - }; - - this._getDisconnectedPublishing = function() { return client.disconnectedPublishing; }; - this._setDisconnectedPublishing = function(newDisconnectedPublishing) { - client.disconnectedPublishing = newDisconnectedPublishing; - }; - - this._getDisconnectedBufferSize = function() { return client.disconnectedBufferSize; }; - this._setDisconnectedBufferSize = function(newDisconnectedBufferSize) { - client.disconnectedBufferSize = newDisconnectedBufferSize; - }; - - this._getOnConnectionLost = function() { return client.onConnectionLost; }; - this._setOnConnectionLost = function(newOnConnectionLost) { - if (typeof newOnConnectionLost === "function") - client.onConnectionLost = newOnConnectionLost; - else - throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnectionLost, "onConnectionLost"])); - }; - - this._getOnMessageDelivered = function() { return client.onMessageDelivered; }; - this._setOnMessageDelivered = function(newOnMessageDelivered) { - if (typeof newOnMessageDelivered === "function") - client.onMessageDelivered = newOnMessageDelivered; - else - throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageDelivered, "onMessageDelivered"])); - }; - - this._getOnMessageArrived = function() { return client.onMessageArrived; }; - this._setOnMessageArrived = function(newOnMessageArrived) { - if (typeof newOnMessageArrived === "function") - client.onMessageArrived = newOnMessageArrived; - else - throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageArrived, "onMessageArrived"])); - }; - - this._getTrace = function() { return client.traceFunction; }; - this._setTrace = function(trace) { - if(typeof trace === "function"){ - client.traceFunction = trace; - }else{ - throw new Error(format(ERROR.INVALID_TYPE, [typeof trace, "onTrace"])); + var clientIdLength = 0; + for (var i = 0; i 65535) + throw new Error(format(ERROR.INVALID_ARGUMENT, [clientId, "clientId"])); + + var client = new ClientImpl(uri, host, port, path, clientId); + + //Public Properties + Object.defineProperties(this,{ + "host":{ + get: function() { return host; }, + set: function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); } + }, + "port":{ + get: function() { return port; }, + set: function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); } + }, + "path":{ + get: function() { return path; }, + set: function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); } + }, + "uri":{ + get: function() { return uri; }, + set: function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); } + }, + "clientId":{ + get: function() { return client.clientId; }, + set: function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); } + }, + "onConnected":{ + get: function() { return client.onConnected; }, + set: function(newOnConnected) { + if (typeof newOnConnected === "function") + client.onConnected = newOnConnected; + else + throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnected, "onConnected"])); + } + }, + "disconnectedPublishing":{ + get: function() { return client.disconnectedPublishing; }, + set: function(newDisconnectedPublishing) { + client.disconnectedPublishing = newDisconnectedPublishing; + } + }, + "disconnectedBufferSize":{ + get: function() { return client.disconnectedBufferSize; }, + set: function(newDisconnectedBufferSize) { + client.disconnectedBufferSize = newDisconnectedBufferSize; + } + }, + "onConnectionLost":{ + get: function() { return client.onConnectionLost; }, + set: function(newOnConnectionLost) { + if (typeof newOnConnectionLost === "function") + client.onConnectionLost = newOnConnectionLost; + else + throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnectionLost, "onConnectionLost"])); + } + }, + "onMessageDelivered":{ + get: function() { return client.onMessageDelivered; }, + set: function(newOnMessageDelivered) { + if (typeof newOnMessageDelivered === "function") + client.onMessageDelivered = newOnMessageDelivered; + else + throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageDelivered, "onMessageDelivered"])); + } + }, + "onMessageArrived":{ + get: function() { return client.onMessageArrived; }, + set: function(newOnMessageArrived) { + if (typeof newOnMessageArrived === "function") + client.onMessageArrived = newOnMessageArrived; + else + throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageArrived, "onMessageArrived"])); + } + }, + "trace":{ + get: function() { return client.traceFunction; }, + set: function(trace) { + if(typeof trace === "function"){ + client.traceFunction = trace; + }else{ + throw new Error(format(ERROR.INVALID_TYPE, [typeof trace, "onTrace"])); + } + } + }, + }); - /** + /** * Connect this Messaging client to its server. * - * @name Paho.MQTT.Client#connect + * @name Paho.Client#connect * @function * @param {object} connectOptions - Attributes used with the connection. * @param {number} connectOptions.timeout - If the connect has not succeeded within this @@ -1853,7 +1877,7 @@ var PahoMQTT = (function (global) { * The default is 30 seconds. * @param {string} connectOptions.userName - Authentication username for this connection. * @param {string} connectOptions.password - Authentication password for this connection. - * @param {Paho.MQTT.Message} connectOptions.willMessage - sent by the server when the client + * @param {Paho.Message} connectOptions.willMessage - sent by the server when the client * disconnects abnormally. * @param {number} connectOptions.keepAliveInterval - the server disconnects this client if * there is no activity for this number of seconds. @@ -1868,152 +1892,152 @@ var PahoMQTT = (function (global) { *
    *
  1. invocationContext as passed in to the onSuccess method in the connectOptions. *
- * @param {function} connectOptions.onFailure - called when the connect request has failed or timed out. + * @param {function} connectOptions.onFailure - called when the connect request has failed or timed out. * A single response object parameter is passed to the onFailure callback containing the following fields: *
    *
  1. invocationContext as passed in to the onFailure method in the connectOptions. *
  2. errorCode a number indicating the nature of the error. *
  3. errorMessage text describing the error. *
- * @param {array} connectOptions.hosts - If present this contains either a set of hostnames or fully qualified + * @param {array} connectOptions.hosts - If present this contains either a set of hostnames or fully qualified * WebSocket URIs (ws://iot.eclipse.org:80/ws), that are tried in order in place * of the host and port paramater on the construtor. The hosts are tried one at at time in order until * one of then succeeds. - * @param {array} connectOptions.ports - If present the set of ports matching the hosts. If hosts contains URIs, this property + * @param {array} connectOptions.ports - If present the set of ports matching the hosts. If hosts contains URIs, this property * is not used. - * @param {boolean} connectOptions.reconnect - Sets whether the client will automatically attempt to reconnect - * to the server if the connection is lost. - *
    - *
  • If set to false, the client will not attempt to automatically reconnect to the server in the event that the - * connection is lost.
  • - *
  • If set to true, in the event that the connection is lost, the client will attempt to reconnect to the server. - * It will initially wait 1 second before it attempts to reconnect, for every failed reconnect attempt, the delay - * will double until it is at 2 minutes at which point the delay will stay at 2 minutes.
  • - *
- * @param {number} connectOptions.mqttVersion - The version of MQTT to use to connect to the MQTT Broker. - *
    - *
  • 3 - MQTT V3.1
  • - *
  • 4 - MQTT V3.1.1
  • - *
- * @param {boolean} connectOptions.mqttVersionExplicit - If set to true, will force the connection to use the - * selected MQTT Version or will fail to connect. - * @param {array} connectOptions.uris - If present, should contain a list of fully qualified WebSocket uris - * (e.g. ws://iot.eclipse.org:80/ws), that are tried in order in place of the host and port parameter of the construtor. - * The uris are tried one at a time in order until one of them succeeds. Do not use this in conjunction with hosts as - * the hosts array will be converted to uris and will overwrite this property. + * @param {boolean} connectOptions.reconnect - Sets whether the client will automatically attempt to reconnect + * to the server if the connection is lost. + *
    + *
  • If set to false, the client will not attempt to automatically reconnect to the server in the event that the + * connection is lost.
  • + *
  • If set to true, in the event that the connection is lost, the client will attempt to reconnect to the server. + * It will initially wait 1 second before it attempts to reconnect, for every failed reconnect attempt, the delay + * will double until it is at 2 minutes at which point the delay will stay at 2 minutes.
  • + *
+ * @param {number} connectOptions.mqttVersion - The version of MQTT to use to connect to the MQTT Broker. + *
    + *
  • 3 - MQTT V3.1
  • + *
  • 4 - MQTT V3.1.1
  • + *
+ * @param {boolean} connectOptions.mqttVersionExplicit - If set to true, will force the connection to use the + * selected MQTT Version or will fail to connect. + * @param {array} connectOptions.uris - If present, should contain a list of fully qualified WebSocket uris + * (e.g. ws://iot.eclipse.org:80/ws), that are tried in order in place of the host and port parameter of the construtor. + * The uris are tried one at a time in order until one of them succeeds. Do not use this in conjunction with hosts as + * the hosts array will be converted to uris and will overwrite this property. * @throws {InvalidState} If the client is not in disconnected state. The client must have received connectionLost * or disconnected before calling connect for a second or subsequent time. */ - this.connect = function (connectOptions) { - connectOptions = connectOptions || {} ; - validate(connectOptions, {timeout:"number", - userName:"string", - password:"string", - willMessage:"object", - keepAliveInterval:"number", - cleanSession:"boolean", - useSSL:"boolean", - invocationContext:"object", - onSuccess:"function", - onFailure:"function", - hosts:"object", - ports:"object", - reconnect:"boolean", - mqttVersion:"number", - mqttVersionExplicit:"boolean", - uris: "object"}); - - // If no keep alive interval is set, assume 60 seconds. - if (connectOptions.keepAliveInterval === undefined) - connectOptions.keepAliveInterval = 60; - - if (connectOptions.mqttVersion > 4 || connectOptions.mqttVersion < 3) { - throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.mqttVersion, "connectOptions.mqttVersion"])); - } + this.connect = function (connectOptions) { + connectOptions = connectOptions || {} ; + validate(connectOptions, {timeout:"number", + userName:"string", + password:"string", + willMessage:"object", + keepAliveInterval:"number", + cleanSession:"boolean", + useSSL:"boolean", + invocationContext:"object", + onSuccess:"function", + onFailure:"function", + hosts:"object", + ports:"object", + reconnect:"boolean", + mqttVersion:"number", + mqttVersionExplicit:"boolean", + uris: "object"}); + + // If no keep alive interval is set, assume 60 seconds. + if (connectOptions.keepAliveInterval === undefined) + connectOptions.keepAliveInterval = 60; + + if (connectOptions.mqttVersion > 4 || connectOptions.mqttVersion < 3) { + throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.mqttVersion, "connectOptions.mqttVersion"])); + } - if (connectOptions.mqttVersion === undefined) { - connectOptions.mqttVersionExplicit = false; - connectOptions.mqttVersion = 4; - } else { - connectOptions.mqttVersionExplicit = true; - } + if (connectOptions.mqttVersion === undefined) { + connectOptions.mqttVersionExplicit = false; + connectOptions.mqttVersion = 4; + } else { + connectOptions.mqttVersionExplicit = true; + } - //Check that if password is set, so is username - if (connectOptions.password !== undefined && connectOptions.userName === undefined) - throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.password, "connectOptions.password"])); + //Check that if password is set, so is username + if (connectOptions.password !== undefined && connectOptions.userName === undefined) + throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.password, "connectOptions.password"])); - if (connectOptions.willMessage) { - if (!(connectOptions.willMessage instanceof Message)) - throw new Error(format(ERROR.INVALID_TYPE, [connectOptions.willMessage, "connectOptions.willMessage"])); - // The will message must have a payload that can be represented as a string. - // Cause the willMessage to throw an exception if this is not the case. - connectOptions.willMessage.stringPayload = null; + if (connectOptions.willMessage) { + if (!(connectOptions.willMessage instanceof Message)) + throw new Error(format(ERROR.INVALID_TYPE, [connectOptions.willMessage, "connectOptions.willMessage"])); + // The will message must have a payload that can be represented as a string. + // Cause the willMessage to throw an exception if this is not the case. + connectOptions.willMessage.stringPayload = null; - if (typeof connectOptions.willMessage.destinationName === "undefined") - throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.willMessage.destinationName, "connectOptions.willMessage.destinationName"])); - } - if (typeof connectOptions.cleanSession === "undefined") - connectOptions.cleanSession = true; - if (connectOptions.hosts) { - - if (!(connectOptions.hosts instanceof Array) ) - throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"])); - if (connectOptions.hosts.length <1 ) - throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"])); - - var usingURIs = false; - for (var i = 0; i * @param {object} subscribeOptions - used to control the subscription * - * @param {number} subscribeOptions.qos - the maiximum qos of any publications sent + * @param {number} subscribeOptions.qos - the maximum qos of any publications sent * as a result of making this subscription. * @param {object} subscribeOptions.invocationContext - passed to the onSuccess callback * or onFailure callback. @@ -2036,32 +2060,32 @@ var PahoMQTT = (function (global) { * callback from being called when the subscribe completes. * @throws {InvalidState} if the client is not in connected state. */ - this.subscribe = function (filter, subscribeOptions) { - if (typeof filter !== "string") - throw new Error("Invalid argument:"+filter); - subscribeOptions = subscribeOptions || {} ; - validate(subscribeOptions, {qos:"number", - invocationContext:"object", - onSuccess:"function", - onFailure:"function", - timeout:"number" - }); - if (subscribeOptions.timeout && !subscribeOptions.onFailure) - throw new Error("subscribeOptions.timeout specified with no onFailure callback."); - if (typeof subscribeOptions.qos !== "undefined" && !(subscribeOptions.qos === 0 || subscribeOptions.qos === 1 || subscribeOptions.qos === 2 )) - throw new Error(format(ERROR.INVALID_ARGUMENT, [subscribeOptions.qos, "subscribeOptions.qos"])); - client.subscribe(filter, subscribeOptions); - }; + this.subscribe = function (filter, subscribeOptions) { + if (typeof filter !== "string" && filter.constructor !== Array) + throw new Error("Invalid argument:"+filter); + subscribeOptions = subscribeOptions || {} ; + validate(subscribeOptions, {qos:"number", + invocationContext:"object", + onSuccess:"function", + onFailure:"function", + timeout:"number" + }); + if (subscribeOptions.timeout && !subscribeOptions.onFailure) + throw new Error("subscribeOptions.timeout specified with no onFailure callback."); + if (typeof subscribeOptions.qos !== "undefined" && !(subscribeOptions.qos === 0 || subscribeOptions.qos === 1 || subscribeOptions.qos === 2 )) + throw new Error(format(ERROR.INVALID_ARGUMENT, [subscribeOptions.qos, "subscribeOptions.qos"])); + client.subscribe(filter, subscribeOptions); + }; /** * Unsubscribe for messages, stop receiving messages sent to destinations described by the filter. * - * @name Paho.MQTT.Client#unsubscribe + * @name Paho.Client#unsubscribe * @function * @param {string} filter - describing the destinations to receive messages from. * @param {object} unsubscribeOptions - used to control the subscription * @param {object} unsubscribeOptions.invocationContext - passed to the onSuccess callback - or onFailure callback. + or onFailure callback. * @param {function} unsubscribeOptions.onSuccess - called when the unsubscribe acknowledgement has been received from the server. * A single response object parameter is passed to the * onSuccess callback containing the following fields: @@ -2081,27 +2105,27 @@ var PahoMQTT = (function (global) { * called when the unsubscribe completes * @throws {InvalidState} if the client is not in connected state. */ - this.unsubscribe = function (filter, unsubscribeOptions) { - if (typeof filter !== "string") - throw new Error("Invalid argument:"+filter); - unsubscribeOptions = unsubscribeOptions || {} ; - validate(unsubscribeOptions, {invocationContext:"object", - onSuccess:"function", - onFailure:"function", - timeout:"number" - }); - if (unsubscribeOptions.timeout && !unsubscribeOptions.onFailure) - throw new Error("unsubscribeOptions.timeout specified with no onFailure callback."); - client.unsubscribe(filter, unsubscribeOptions); - }; + this.unsubscribe = function (filter, unsubscribeOptions) { + if (typeof filter !== "string" && filter.constructor !== Array) + throw new Error("Invalid argument:"+filter); + unsubscribeOptions = unsubscribeOptions || {} ; + validate(unsubscribeOptions, {invocationContext:"object", + onSuccess:"function", + onFailure:"function", + timeout:"number" + }); + if (unsubscribeOptions.timeout && !unsubscribeOptions.onFailure) + throw new Error("unsubscribeOptions.timeout specified with no onFailure callback."); + client.unsubscribe(filter, unsubscribeOptions); + }; - /** + /** * Send a message to the consumers of the destination in the Message. * - * @name Paho.MQTT.Client#send + * @name Paho.Client#send * @function - * @param {string|Paho.MQTT.Message} topic - mandatory The name of the destination to which the message is to be sent. - * - If it is the only parameter, used as Paho.MQTT.Message object. + * @param {string|Paho.Message} topic - mandatory The name of the destination to which the message is to be sent. + * - If it is the only parameter, used as Paho.Message object. * @param {String|ArrayBuffer} payload - The message data to be sent. * @param {number} qos The Quality of Service used to deliver the message. *
@@ -2117,42 +2141,42 @@ var PahoMQTT = (function (global) { * and the subscrption was made after the message has been published. * @throws {InvalidState} if the client is not connected. */ - this.send = function (topic,payload,qos,retained) { - var message ; + this.send = function (topic,payload,qos,retained) { + var message ; - if(arguments.length === 0){ - throw new Error("Invalid argument."+"length"); + if(arguments.length === 0){ + throw new Error("Invalid argument."+"length"); - }else if(arguments.length == 1) { + }else if(arguments.length == 1) { - if (!(topic instanceof Message) && (typeof topic !== "string")) - throw new Error("Invalid argument:"+ typeof topic); + if (!(topic instanceof Message) && (typeof topic !== "string")) + throw new Error("Invalid argument:"+ typeof topic); - message = topic; - if (typeof message.destinationName === "undefined") - throw new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,"Message.destinationName"])); - client.send(message); + message = topic; + if (typeof message.destinationName === "undefined") + throw new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,"Message.destinationName"])); + client.send(message); - }else { + }else { //parameter checking in Message object - message = new Message(payload); - message.destinationName = topic; - if(arguments.length >= 3) - message.qos = qos; - if(arguments.length >= 4) - message.retained = retained; - client.send(message); - } - }; + message = new Message(payload); + message.destinationName = topic; + if(arguments.length >= 3) + message.qos = qos; + if(arguments.length >= 4) + message.retained = retained; + client.send(message); + } + }; - /** + /** * Publish a message to the consumers of the destination in the Message. * Synonym for Paho.Mqtt.Client#send * - * @name Paho.MQTT.Client#publish + * @name Paho.Client#publish * @function - * @param {string|Paho.MQTT.Message} topic - mandatory The name of the topic to which the message is to be published. - * - If it is the only parameter, used as Paho.MQTT.Message object. + * @param {string|Paho.Message} topic - mandatory The name of the topic to which the message is to be published. + * - If it is the only parameter, used as Paho.Message object. * @param {String|ArrayBuffer} payload - The message data to be published. * @param {number} qos The Quality of Service used to deliver the message. *
@@ -2168,124 +2192,87 @@ var PahoMQTT = (function (global) { * and the subscrption was made after the message has been published. * @throws {InvalidState} if the client is not connected. */ - this.publish = function(topic,payload,qos,retained) { - console.log("Publising message to: ", topic); - var message ; - - if(arguments.length === 0){ - throw new Error("Invalid argument."+"length"); - - }else if(arguments.length == 1) { - - if (!(topic instanceof Message) && (typeof topic !== "string")) - throw new Error("Invalid argument:"+ typeof topic); - - message = topic; - if (typeof message.destinationName === "undefined") - throw new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,"Message.destinationName"])); - client.send(message); - - }else { - //parameter checking in Message object - message = new Message(payload); - message.destinationName = topic; - if(arguments.length >= 3) - message.qos = qos; - if(arguments.length >= 4) - message.retained = retained; - client.send(message); - } - }; + this.publish = function(topic,payload,qos,retained) { + var message ; + + if(arguments.length === 0){ + throw new Error("Invalid argument."+"length"); + + }else if(arguments.length == 1) { + + if (!(topic instanceof Message) && (typeof topic !== "string")) + throw new Error("Invalid argument:"+ typeof topic); + + message = topic; + if (typeof message.destinationName === "undefined") + throw new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,"Message.destinationName"])); + client.send(message); + + }else { + //parameter checking in Message object + message = new Message(payload); + message.destinationName = topic; + if(arguments.length >= 3) + message.qos = qos; + if(arguments.length >= 4) + message.retained = retained; + client.send(message); + } + }; - /** + /** * Normal disconnect of this Messaging client from its server. * - * @name Paho.MQTT.Client#disconnect + * @name Paho.Client#disconnect * @function * @throws {InvalidState} if the client is already disconnected. */ - this.disconnect = function () { - client.disconnect(); - }; + this.disconnect = function () { + client.disconnect(); + }; - /** + /** * Get the contents of the trace log. * - * @name Paho.MQTT.Client#getTraceLog + * @name Paho.Client#getTraceLog * @function * @return {Object[]} tracebuffer containing the time ordered trace records. */ - this.getTraceLog = function () { - return client.getTraceLog(); - }; + this.getTraceLog = function () { + return client.getTraceLog(); + }; - /** + /** * Start tracing. * - * @name Paho.MQTT.Client#startTrace + * @name Paho.Client#startTrace * @function */ - this.startTrace = function () { - client.startTrace(); - }; + this.startTrace = function () { + client.startTrace(); + }; - /** + /** * Stop tracing. * - * @name Paho.MQTT.Client#stopTrace + * @name Paho.Client#stopTrace * @function */ - this.stopTrace = function () { - client.stopTrace(); - }; + this.stopTrace = function () { + client.stopTrace(); + }; - this.isConnected = function() { - return client.connected; + this.isConnected = function() { + return client.connected; + }; }; - }; - - Client.prototype = { - get host() { return this._getHost(); }, - set host(newHost) { this._setHost(newHost); }, - - get port() { return this._getPort(); }, - set port(newPort) { this._setPort(newPort); }, - - get path() { return this._getPath(); }, - set path(newPath) { this._setPath(newPath); }, - - get clientId() { return this._getClientId(); }, - set clientId(newClientId) { this._setClientId(newClientId); }, - - get onConnected() { return this._getOnConnected(); }, - set onConnected(newOnConnected) { this._setOnConnected(newOnConnected); }, - - get disconnectedPublishing() { return this._getDisconnectedPublishing(); }, - set disconnectedPublishing(newDisconnectedPublishing) { this._setDisconnectedPublishing(newDisconnectedPublishing); }, - get disconnectedBufferSize() { return this._getDisconnectedBufferSize(); }, - set disconnectedBufferSize(newDisconnectedBufferSize) { this._setDisconnectedBufferSize(newDisconnectedBufferSize); }, - - get onConnectionLost() { return this._getOnConnectionLost(); }, - set onConnectionLost(newOnConnectionLost) { this._setOnConnectionLost(newOnConnectionLost); }, - - get onMessageDelivered() { return this._getOnMessageDelivered(); }, - set onMessageDelivered(newOnMessageDelivered) { this._setOnMessageDelivered(newOnMessageDelivered); }, - - get onMessageArrived() { return this._getOnMessageArrived(); }, - set onMessageArrived(newOnMessageArrived) { this._setOnMessageArrived(newOnMessageArrived); }, - - get trace() { return this._getTrace(); }, - set trace(newTraceFunction) { this._setTrace(newTraceFunction); } - - }; - - /** + /** * An application message, sent or received. *

* All attributes may be null, which implies the default values. * - * @name Paho.MQTT.Message + * @name Paho.Message * @constructor * @param {String|ArrayBuffer} payload The message data to be sent. *

@@ -2314,100 +2301,95 @@ var PahoMQTT = (function (global) { * This is only set on messages received from the server. * */ - var Message = function (newPayload) { - var payload; - if ( typeof newPayload === "string" || + var Message = function (newPayload) { + var payload; + if ( typeof newPayload === "string" || newPayload instanceof ArrayBuffer || - newPayload instanceof Int8Array || - newPayload instanceof Uint8Array || - newPayload instanceof Int16Array || - newPayload instanceof Uint16Array || - newPayload instanceof Int32Array || - newPayload instanceof Uint32Array || - newPayload instanceof Float32Array || - newPayload instanceof Float64Array - ) { - payload = newPayload; - } else { - throw (format(ERROR.INVALID_ARGUMENT, [newPayload, "newPayload"])); - } - - this._getPayloadString = function () { - if (typeof payload === "string") - return payload; - else - return parseUTF8(payload, 0, payload.length); - }; - - this._getPayloadBytes = function() { - if (typeof payload === "string") { - var buffer = new ArrayBuffer(UTF8Length(payload)); - var byteStream = new Uint8Array(buffer); - stringToUTF8(payload, byteStream, 0); - - return byteStream; + (ArrayBuffer.isView(newPayload) && !(newPayload instanceof DataView)) + ) { + payload = newPayload; } else { - return payload; + throw (format(ERROR.INVALID_ARGUMENT, [newPayload, "newPayload"])); } - }; - - var destinationName; - this._getDestinationName = function() { return destinationName; }; - this._setDestinationName = function(newDestinationName) { - if (typeof newDestinationName === "string") - destinationName = newDestinationName; - else - throw new Error(format(ERROR.INVALID_ARGUMENT, [newDestinationName, "newDestinationName"])); - }; - var qos = 0; - this._getQos = function() { return qos; }; - this._setQos = function(newQos) { - if (newQos === 0 || newQos === 1 || newQos === 2 ) - qos = newQos; - else - throw new Error("Invalid argument:"+newQos); + var destinationName; + var qos = 0; + var retained = false; + var duplicate = false; + + Object.defineProperties(this,{ + "payloadString":{ + enumerable : true, + get : function () { + if (typeof payload === "string") + return payload; + else + return parseUTF8(payload, 0, payload.length); + } + }, + "payloadBytes":{ + enumerable: true, + get: function() { + if (typeof payload === "string") { + var buffer = new ArrayBuffer(UTF8Length(payload)); + var byteStream = new Uint8Array(buffer); + stringToUTF8(payload, byteStream, 0); + + return byteStream; + } else { + return payload; + } + } + }, + "destinationName":{ + enumerable: true, + get: function() { return destinationName; }, + set: function(newDestinationName) { + if (typeof newDestinationName === "string") + destinationName = newDestinationName; + else + throw new Error(format(ERROR.INVALID_ARGUMENT, [newDestinationName, "newDestinationName"])); + } + }, + "qos":{ + enumerable: true, + get: function() { return qos; }, + set: function(newQos) { + if (newQos === 0 || newQos === 1 || newQos === 2 ) + qos = newQos; + else + throw new Error("Invalid argument:"+newQos); + } + }, + "retained":{ + enumerable: true, + get: function() { return retained; }, + set: function(newRetained) { + if (typeof newRetained === "boolean") + retained = newRetained; + else + throw new Error(format(ERROR.INVALID_ARGUMENT, [newRetained, "newRetained"])); + } + }, + "topic":{ + enumerable: true, + get: function() { return destinationName; }, + set: function(newTopic) {destinationName=newTopic;} + }, + "duplicate":{ + enumerable: true, + get: function() { return duplicate; }, + set: function(newDuplicate) {duplicate=newDuplicate;} + } + }); }; - var retained = false; - this._getRetained = function() { return retained; }; - this._setRetained = function(newRetained) { - if (typeof newRetained === "boolean") - retained = newRetained; - else - throw new Error(format(ERROR.INVALID_ARGUMENT, [newRetained, "newRetained"])); + // Module contents. + return { + Client: Client, + Message: Message }; - - var duplicate = false; - this._getDuplicate = function() { return duplicate; }; - this._setDuplicate = function(newDuplicate) { duplicate = newDuplicate; }; - }; - - Message.prototype = { - get payloadString() { return this._getPayloadString(); }, - get payloadBytes() { return this._getPayloadBytes(); }, - - get destinationName() { return this._getDestinationName(); }, - set destinationName(newDestinationName) { this._setDestinationName(newDestinationName); }, - - get topic() { return this._getDestinationName(); }, - set topic(newTopic) { this._setDestinationName(newTopic); }, - - get qos() { return this._getQos(); }, - set qos(newQos) { this._setQos(newQos); }, - - get retained() { return this._getRetained(); }, - set retained(newRetained) { this._setRetained(newRetained); }, - - get duplicate() { return this._getDuplicate(); }, - set duplicate(newDuplicate) { this._setDuplicate(newDuplicate); } - }; - - // Module contents. - return { - Client: Client, - Message: Message - }; -})(window); -return PahoMQTT; + // eslint-disable-next-line no-nested-ternary + })(typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); + return PahoMQTT; }); diff --git a/src/test/BasicTest-spec.js b/src/test/BasicTest-spec.js index 44dd24c..e6845c4 100644 --- a/src/test/BasicTest-spec.js +++ b/src/test/BasicTest-spec.js @@ -6,371 +6,509 @@ var testPort = settings.port; var testPath = settings.path; var testMqttVersion = settings.mqttVersion; var topicPrefix = settings.topicPrefix; - -var genStr = function(str){ - var time = new Date(); - return str + '.' + time.getTime(); +var testUseSSL = settings.useSSL; +var genStr = function(str) { + var time = new Date(); + return str + '.' + time.getTime(); }; describe('BasicTest', function() { - //var client = null; - var clientId = this.description; - var connected = false; - var failure = false; - var disconnectError = null; - var disconnectErrorMsg = null; - - var subscribed = false; - var isMessageReceived = false; - var isMessageDelivered = false; - var strTopic = topicPrefix + '/' + makeid() + '/World'; - var strMessageReceived = ''; - var strMessageSend = 'Hello'; - var strTopicReceived = ''; - - - function makeid(){ + //var client = null; + var clientId = this.description; + var connected = false; + var failure = false; + var disconnectError = null; + var disconnectErrorMsg = null; + + var subscribed = false; + var isMessageReceived = false; + var isMessageDelivered = false; + var strTopic = topicPrefix + '/' + makeid() + '/World'; + var strTopic2 = topicPrefix + '/' + makeid() + '/Mars'; + var strMessageReceived = ''; + var strMessageSend = 'Hello'; + var strMessageSend2 = '你好'; // Hello in Traditional Chinese and a good UTF-8 test + var strTopicReceived = ''; + + settings.printConfig(settings); + + + function makeid() { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for( var i=0; i < 5; i++ ) - text += possible.charAt(Math.floor(Math.random() * possible.length)); + for (var i = 0; i < 5; i++) + text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; - } - - function onConnectSuccess(responseObj) { - //console.log("connected %s",responseObj); - connected = true; - }; - - function onConnectionLost(err) { - connected = false; - if(err){ - disconnectError = err.errorCode; - disconnectErrorMsg = err.errorMessage; - } - console.log("connection lost. ErrorCode: %s, ErrorMsg: %s",disconnectError,disconnectErrorMsg); - }; - - function onConnectFailure(err){ - connected = false; - console.log('Connect fail. ErrorCode: %s, ErrorMsg: %s',err.errCode,err.errorMessage); - }; - - function onSubscribeSuccess() { - subscribed = true; - //console.log("subscribed",subscribed); - }; - - function onSubscribeFailure(err) { - subscribed = false; - console.log("subscribe fail. ErrorCode: %s, ErrorMsg: %s",err.errCode,err.errorMessage); - }; - function onUnsubscribeSuccess() { - subscribed = false; - //console.log("unsubscribed",subscribed); - }; - - function messageDelivered(response) { - console.log("messageDelivered. topic:%s, duplicate:%s, payloadString:%s,qos:%s,retained:%s",response.destinationName,response.duplicate,response.payloadString,response.qos,response.retained); - isMessageDelivered = true; - //reponse.invocationContext.onMessageArrived = null; - }; - - function messageArrived(message) { - console.log("messageArrived.",'topic:',message.destinationName,' ;content:',message.payloadString); - isMessageReceived = true; - strMessageReceived = message.payloadString; - strTopicReceived = message.destinationName; - - //reponse.invocationContext.onMessageArrived = null; - }; - - beforeEach(function(){ - connected = false; - failure = false; - disconnectError = null; - disconnectErrorMsg = null; - //if(!client){ - // client = new Paho.Client(testServer, testPort, clientId); - //} - }); - - /*afterEach(function(){ - if(client){ - client.disconnect(); - } - }); - */ - it('it should create and connect and disconnect to a server.', function() { - var client = new Paho.Client(testServer, testPort, testPath, genStr(clientId)); - client.onConnectionLost = onConnectionLost; - expect(client).not.toBe(null); - - - console.log('Connecting...'); - runs(function() { - client.connect({onSuccess:onConnectSuccess,onFailure:onConnectFailure,mqttVersion:testMqttVersion, useSSL: true}); - }); - waitsFor(function() { - return connected; - }, "the client should connect", 2000); - runs(function() { - expect(connected).toBe(true); - }); - - console.log('Disconnecting...'); - runs(function() { - client.disconnect(); - }); - waitsFor(function() { - return (connected==false); - }, "the client should disconnect", 2000); - runs(function() { - expect(connected).not.toBe(true); - }); - - console.log('Re-Connecting...'); - runs(function() { - client.connect({onSuccess:onConnectSuccess,mqttVersion:testMqttVersion, useSSL: true}); - }); - - waitsFor(function() { - return connected; - }, "the client should re-connect", 2000); - - runs(function() { - expect(connected).toBe(true); - }); - - }); - - it('it should fallback from MQTTv3.1.1 to v3.1',function(){ - var client = new Paho.Client(testServer, testPort, testPath, genStr(clientId)); - client.onConnectionLost = onConnectionLost; - expect(client).not.toBe(null); - - console.log('Connecting...'); - runs(function() { - client.connect({onSuccess:onConnectSuccess,onFailure:onConnectFailure,timeout:1, useSSL: true}); - }); - waitsFor(function() { - return connected; - }, "the client should connect", 4000); - runs(function() { - expect(connected).toBe(true); - }); - - console.log('Disconnecting...'); - runs(function() { - client.disconnect(); - }); - waitsFor(function() { - return (connected==false); - }, "the client should disconnect", 2000); - runs(function() { - expect(connected).not.toBe(true); - }); - }); - - it('it should connect to a list of server(HA connection).',function(){ - var defaultServer = testServer; - var defaultPort = testPort; - var arrHosts = ['localhost',testServer,]; - var arrPorts = [2000,testPort]; - - var client = new Paho.Client(defaultServer, defaultPort, testPath, genStr(clientId) ); - client.onConnectionLost = onConnectionLost; - expect(client).not.toBe(null); - - console.log('should connect to a available server from list'); - runs(function() { - client.connect({ - onSuccess : onConnectSuccess, - onFailure : onConnectFailure, - hosts : arrHosts, - ports : arrPorts, - mqttVersion: testMqttVersion, - useSSL : true - }); - }); - - waitsFor(function() { - return connected; - }, "the client should connect", 2000); - - runs(function() { - expect(connected).toBe(true); - //client.disconnect(); - }); - - }); - - - it('it should publish and subscribe.',function(){ - - var client = new Paho.Client(testServer, testPort, testPath, genStr(clientId)); - client.onMessageArrived = messageArrived; - client.onMessageDelivered = messageDelivered; - - runs(function() { - client.connect({onSuccess:onConnectSuccess,onFailure:onConnectFailure,mqttVersion:testMqttVersion, useSSL: true}); - }); - waitsFor(function() { - return connected; - }, "the client should connect", 5000); - runs(function() { - expect(connected).toBe(true); - }); - - console.log('Subscribe a topic...'); - runs(function() { - client.subscribe(strTopic, {onSuccess:onSubscribeSuccess,onFailure:onSubscribeFailure}); - }); - - waitsFor(function() { - return subscribed; - }, "the client should subscribe", 2000); - - runs(function() { - expect(subscribed).toBe(true); - }); - - console.log('Send and receive message...'); - runs(function() { - var message = new Paho.Message(strMessageSend); - message.destinationName = strTopic; - client.send(message); - }); - waitsFor(function() { - return isMessageReceived; - }, "the client should send and receive a message", 2000); - runs(function() { - //to do Check message sent - expect(isMessageDelivered).toBe(true); - //Check msg received - expect(isMessageReceived).toBe(true); - //Check message - expect(strMessageReceived).toEqual(strMessageSend); - //Check topic - expect(strTopicReceived).toEqual(strTopic); - - //disconnect - //client.disconnect(); - - }); - - console.log('Unsubscribe a topic...'); - runs(function() { - client.unsubscribe(strTopic, {onSuccess:onUnsubscribeSuccess}); - }); - waitsFor(function() { - return !subscribed; - }, "the client should subscribe", 2000); - runs(function() { - expect(subscribed).toBe(false); - //disconnect - //client.disconnect(); - }); - - //should not receive a message after unsubscribe - runs(function() { - //console.log('isMessageReceived',isMessageReceived); - isMessageDelivered = false; - isMessageReceived = false; - strMessageReceived = ''; - strTopicReceived = ''; - - var message = new Paho.Message(genStr(strMessageSend)); - message.destinationName = strTopic; - client.send(message); - }) - waitsFor(function() { - return isMessageDelivered; - }, "the client should send and receive a message", 2000); - - runs(function() { - //to do Check message sent - expect(isMessageDelivered).toBe(true); - //Check msg received - expect(isMessageReceived).toBe(false); - //Check message - expect(strMessageReceived).toEqual(''); - //Check topic - expect(strTopicReceived).toEqual(''); - - //disconnect - //client.disconnect(); - - }) - }); - - it('check message properties.',function(){ - var strMsg = 'test Msg'; - var strDes = topicPrefix + '/test'; - var message = new Paho.Message(strMsg); - message.destinationName = strDes; - - console.log('Check default for message with payload.'); - expect(message.qos).toBe(0); - expect(message.duplicate).toBe(false); - expect(message.retained).toBe(false); - expect(message.payloadString).toEqual(strMsg); - expect(message.payloadBytes.length).toBeGreaterThan(0); - expect(message.destinationName).toEqual(strDes); - - console.log('Check empty msg to throw error'); - expect(function(){ - var empMsg = new Paho.Message(); - }).toThrow(); - - console.log('Check message qos'); - message.qos = 0; - expect(message.qos).toBe(0); - message.qos = 1; - expect(message.qos).toBe(1); - message.qos = 2; - expect(message.qos).toBe(2); - - //illegal argument exception - expect(function(){ - message.qos = -1; - }).toThrow(); - expect(function(){ - message.qos = 1; - }).not.toThrow(); - - console.log('Check payload'); - var strPayload = 'payload is a string'; - message.payloadString = strPayload; - console.log('not allowed to set payload'); - expect(message.payloadString).not.toEqual(strPayload); - - console.log('Check retained'); - message.retained = false; - expect(message.retained).toBe(false); - message.retained = true; - expect(message.retained).toBe(true); - - console.log('Check duplicate'); - message.duplicate = false; - expect(message.duplicate).toBe(false); - message.duplicate = true; - expect(message.duplicate).toBe(true); - - //to do , check payload - /* - var buffer = new ArrayBuffer(4); - var uintArr = new Uint8Array(buffer); - dataView = new DataView(buffer); - dataView.setInt32(0,0x48656c6c); - //dataView.setInt - console.log(dataView.getInt32(0).toString(16)); - //var arrbufPayload = new ArrayBuffer - var msg = new Paho.Message(buffer); - console.log(msg.payloadBytes,msg.payloadString); - */ - }); + } + + function onConnectSuccess(responseObj) { + //console.log("connected %s",responseObj); + connected = true; + }; + + function onConnectionLost(err) { + connected = false; + if (err) { + disconnectError = err.errorCode; + disconnectErrorMsg = err.errorMessage; + } + console.log("connection lost. ErrorCode: %s, ErrorMsg: %s", disconnectError, disconnectErrorMsg); + }; + + function onConnectFailure(err) { + connected = false; + console.log('Connect fail. ErrorCode: %s, ErrorMsg: %s', err.errCode, err.errorMessage); + }; + + function onSubscribeSuccess() { + subscribed = true; + //console.log("subscribed",subscribed); + }; + + function onSubscribeFailure(err) { + subscribed = false; + console.log("subscribe fail. ErrorCode: %s, ErrorMsg: %s", err.errCode, err.errorMessage); + }; + + function onUnsubscribeSuccess() { + subscribed = false; + //console.log("unsubscribed",subscribed); + }; + + function messageDelivered(response) { + console.log("messageDelivered. topic:%s, duplicate:%s, payloadString:%s,qos:%s,retained:%s", response.destinationName, response.duplicate, response.payloadString, response.qos, response.retained); + isMessageDelivered = true; + //reponse.invocationContext.onMessageArrived = null; + }; + + function messageArrived(message) { + console.log("messageArrived.", 'topic:', message.destinationName, ' ;content:', message.payloadString); + isMessageReceived = true; + strMessageReceived = message.payloadString; + strTopicReceived = message.destinationName; + + //reponse.invocationContext.onMessageArrived = null; + }; + + beforeEach(function() { + connected = false; + failure = false; + disconnectError = null; + disconnectErrorMsg = null; + //if(!client){ + // client = new Paho.Client(testServer, testPort, clientId); + //} + }); + + /*afterEach(function(){ + if(client){ + client.disconnect(); + } + }); + */ + it('it should create and connect and disconnect to a server.', function() { + var client = new Paho.Client(testServer, testPort, testPath, genStr(clientId)); + client.onConnectionLost = onConnectionLost; + expect(client).not.toBe(null); + + + console.log('Connecting...'); + runs(function() { + client.connect({ + onSuccess: onConnectSuccess, + onFailure: onConnectFailure, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + waitsFor(function() { + return connected; + }, "the client should connect", 2000); + runs(function() { + expect(connected).toBe(true); + }); + + console.log('Disconnecting...'); + runs(function() { + client.disconnect(); + }); + waitsFor(function() { + return (connected == false); + }, "the client should disconnect", 2000); + runs(function() { + expect(connected).not.toBe(true); + }); + + console.log('Re-Connecting...'); + runs(function() { + client.connect({ + onSuccess: onConnectSuccess, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + + waitsFor(function() { + return connected; + }, "the client should re-connect", 2000); + + runs(function() { + expect(connected).toBe(true); + }); + + }); + + it('it should fallback from MQTTv3.1.1 to v3.1', function() { + var client = new Paho.Client(testServer, testPort, testPath, genStr(clientId)); + client.onConnectionLost = onConnectionLost; + expect(client).not.toBe(null); + + console.log('Connecting...'); + runs(function() { + client.connect({ + onSuccess: onConnectSuccess, + onFailure: onConnectFailure, + timeout: 1, + useSSL: testUseSSL + }); + }); + waitsFor(function() { + return connected; + }, "the client should connect", 4000); + runs(function() { + expect(connected).toBe(true); + }); + + console.log('Disconnecting...'); + runs(function() { + client.disconnect(); + }); + waitsFor(function() { + return (connected == false); + }, "the client should disconnect", 2000); + runs(function() { + expect(connected).not.toBe(true); + }); + }); + + it('it should connect to a list of server(HA connection).', function() { + var defaultServer = testServer; + var defaultPort = testPort; + var arrHosts = ['localhost', testServer, ]; + var arrPorts = [2000, testPort]; + + var client = new Paho.Client(defaultServer, defaultPort, testPath, genStr(clientId)); + client.onConnectionLost = onConnectionLost; + expect(client).not.toBe(null); + + console.log('should connect to a available server from list'); + runs(function() { + client.connect({ + onSuccess: onConnectSuccess, + onFailure: onConnectFailure, + hosts: arrHosts, + ports: arrPorts, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + + waitsFor(function() { + return connected; + }, "the client should connect", 2000); + + runs(function() { + expect(connected).toBe(true); + //client.disconnect(); + }); + + }); + + + it('it should publish and subscribe.', function() { + + var client = new Paho.Client(testServer, testPort, testPath, genStr(clientId)); + client.onMessageArrived = messageArrived; + client.onMessageDelivered = messageDelivered; + + runs(function() { + client.connect({ + onSuccess: onConnectSuccess, + onFailure: onConnectFailure, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + waitsFor(function() { + return connected; + }, "the client should connect", 5000); + runs(function() { + expect(connected).toBe(true); + }); + + console.log('Subscribe a topic...'); + runs(function() { + client.subscribe(strTopic, { + onSuccess: onSubscribeSuccess, + onFailure: onSubscribeFailure + }); + }); + + waitsFor(function() { + return subscribed; + }, "the client should subscribe", 2000); + + runs(function() { + expect(subscribed).toBe(true); + }); + + console.log('Send and receive message...'); + runs(function() { + var message = new Paho.Message(strMessageSend); + message.destinationName = strTopic; + client.send(message); + }); + waitsFor(function() { + return isMessageReceived; + }, "the client should send and receive a message", 2000); + runs(function() { + //to do Check message sent + expect(isMessageDelivered).toBe(true); + //Check msg received + expect(isMessageReceived).toBe(true); + //Check message + expect(strMessageReceived).toEqual(strMessageSend); + //Check topic + expect(strTopicReceived).toEqual(strTopic); + + //disconnect + //client.disconnect(); + + }); + + console.log('Unsubscribe a topic...'); + runs(function() { + client.unsubscribe(strTopic, { + onSuccess: onUnsubscribeSuccess + }); + }); + waitsFor(function() { + return !subscribed; + }, "the client should subscribe", 2000); + runs(function() { + expect(subscribed).toBe(false); + //disconnect + //client.disconnect(); + }); + + //should not receive a message after unsubscribe + runs(function() { + //console.log('isMessageReceived',isMessageReceived); + isMessageDelivered = false; + isMessageReceived = false; + strMessageReceived = ''; + strTopicReceived = ''; + + var message = new Paho.Message(genStr(strMessageSend)); + message.destinationName = strTopic; + client.send(message); + }) + waitsFor(function() { + return isMessageDelivered; + }, "the client should send and receive a message", 2000); + + runs(function() { + //to do Check message sent + expect(isMessageDelivered).toBe(true); + //Check msg received + expect(isMessageReceived).toBe(false); + //Check message + expect(strMessageReceived).toEqual(''); + //Check topic + expect(strTopicReceived).toEqual(''); + + //disconnect + //client.disconnect(); + + }) + }); + + it('check message properties.', function() { + var strMsg = 'test Msg'; + var strDes = topicPrefix + '/test'; + var message = new Paho.Message(strMsg); + message.destinationName = strDes; + + console.log('Check default for message with payload.'); + expect(message.qos).toBe(0); + expect(message.duplicate).toBe(false); + expect(message.retained).toBe(false); + expect(message.payloadString).toEqual(strMsg); + expect(message.payloadBytes.length).toBeGreaterThan(0); + expect(message.destinationName).toEqual(strDes); + + console.log('Check empty msg to throw error'); + expect(function() { + var empMsg = new Paho.Message(); + }).toThrow(); + + console.log('Check message qos'); + message.qos = 0; + expect(message.qos).toBe(0); + message.qos = 1; + expect(message.qos).toBe(1); + message.qos = 2; + expect(message.qos).toBe(2); + + //illegal argument exception + expect(function() { + message.qos = -1; + }).toThrow(); + expect(function() { + message.qos = 1; + }).not.toThrow(); + + console.log('Check payload'); + var strPayload = 'payload is a string'; + message.payloadString = strPayload; + console.log('not allowed to set payload'); + expect(message.payloadString).not.toEqual(strPayload); + + console.log('Check retained'); + message.retained = false; + expect(message.retained).toBe(false); + message.retained = true; + expect(message.retained).toBe(true); + + console.log('Check duplicate'); + message.duplicate = false; + expect(message.duplicate).toBe(false); + message.duplicate = true; + expect(message.duplicate).toBe(true); + + //to do , check payload + /* + var buffer = new ArrayBuffer(4); + var uintArr = new Uint8Array(buffer); + dataView = new DataView(buffer); + dataView.setInt32(0,0x48656c6c); + //dataView.setInt + console.log(dataView.getInt32(0).toString(16)); + //var arrbufPayload = new ArrayBuffer + var msg = new Paho.Message(buffer); + console.log(msg.payloadBytes,msg.payloadString); + */ + }); + + it('it should subscribe to multiple topics.', function() { + + var client = new Paho.Client(testServer, testPort, testPath, genStr(clientId)); + client.onMessageArrived = messageArrived; + client.onMessageDelivered = messageDelivered; + + runs(function() { + client.connect({ + onSuccess: onConnectSuccess, + onFailure: onConnectFailure, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + waitsFor(function() { + return connected; + }, "the client should connect", 5000); + runs(function() { + expect(connected).toBe(true); + }); + + console.log('Subscribe multiple topics...'); + runs(function() { + client.subscribe([strTopic, strTopic2], { + onSuccess: onSubscribeSuccess, + onFailure: onSubscribeFailure + }); + }); + + waitsFor(function() { + return subscribed; + }, "the client should subscribe to multiple topics.", 2000); + + runs(function() { + expect(subscribed).toBe(true); + }); + + console.log('Send and receive message to the first topic...'); + runs(function() { + var message= new Paho.Message(strMessageSend); + message.destinationName = strTopic; + client.send(message); + }); + + waitsFor(function() { + return isMessageReceived; + }, "the client should send and receive a message", 2000); + runs(function() { + //to do Check message sent + expect(isMessageDelivered).toBe(true); + //Check msg received + expect(isMessageReceived).toBe(true); + //Check message + expect(strMessageReceived).toEqual(strMessageSend); + //Check topic + expect(strTopicReceived).toEqual(strTopic); + + //disconnect + //client.disconnect(); + + }); + + waitsFor(function() { + return isMessageReceived; + }, "Send and receive message to the second topic...", 2000); + runs(function() { + isMessageReceived = false; + var message= new Paho.Message(strMessageSend2); + message.destinationName = strTopic2; + client.send(message); + + }); + + + + waitsFor(function() { + return isMessageReceived; + }, "the client should send and receive a message", 2000); + runs(function() { + //to do Check message sent + expect(isMessageDelivered).toBe(true); + //Check msg received + expect(isMessageReceived).toBe(true); + //Check message + expect(strMessageReceived).toEqual(strMessageSend2); + //Check topic + expect(strTopicReceived).toEqual(strTopic2); + + //disconnect + //client.disconnect(); + + }); + + console.log('Unsubscribe from both topics...'); + runs(function() { + client.unsubscribe([strTopic, strTopic2], { + onSuccess: onUnsubscribeSuccess + }); + }); + waitsFor(function() { + return !subscribed; + }, "the client should subscribe", 2000); + runs(function() { + expect(subscribed).toBe(false); + //disconnect + //client.disconnect(); + }); + }); }); diff --git a/src/test/base-spec.js b/src/test/base-spec.js index da5e704..e332b14 100644 --- a/src/test/base-spec.js +++ b/src/test/base-spec.js @@ -1,82 +1,89 @@ -var settings = require('./client-harness'); +var settings = require('./client-harness') -var testServer = settings.server; -var testPort = settings.port; -var testPath = settings.path; -var testMqttVersion = settings.mqttVersion; -var topicPrefix = settings.topicPrefix; +var testServer = settings.server +var testPort = settings.port +var testPath = settings.path +var testMqttVersion = settings.mqttVersion +var topicPrefix = settings.topicPrefix +var testUseSSL = settings.useSSL describe('client', function() { - var client = this; - var connected = false; - var subscribed = false; - var messageReceived = false; - - function onConnect() { - connected = true; - }; - - function onSubscribe() { - subscribed = true; - }; - - function messageArrived(response) { - messageReceived = true; - //reponse.invocationContext.onMessageArrived = null; - }; - - it('should create a new client', function() { - client = new settings.Paho.Client(testServer, testPort, testPath, "testclientid"); - client.onMessageArrived = messageArrived; - - expect(client).not.toBe(null); - expect(client.host).toBe(testServer); - expect(client.port).toBe(testPort); - expect(client.path).toBe(testPath); - }); - - it('should connect to a server', function() { - runs(function() { - client.connect({onSuccess:onConnect, mqttVersion:testMqttVersion, useSSL: true}); - }); - - waitsFor(function() { - return connected; - }, "the client should connect", 10000); - - runs(function() { - expect(connected).toBe(true); - }); - }); - - it('should subscribe to a topic', function() { - runs(function() { - client.subscribe(topicPrefix + "/World", {onSuccess:onSubscribe}); - }); - - waitsFor(function() { - return subscribed; - }, "the client should subscribe", 2000); - - runs(function() { - expect(subscribed).toBe(true); - }); - }); - - it('should send and receive a message', function() { - runs(function() { - message = new settings.Paho.Message("Hello"); - message.destinationName = topicPrefix + "/World"; - client.send(message); - }) - - waitsFor(function() { - return messageReceived; - }, "the client should send and receive a message", 2000); - - runs(function() { - expect(messageReceived).toBe(true); - }) - }); + var client = this; + var connected = false; + var subscribed = false; + var messageReceived = false; + + function onConnect() { + connected = true; + }; + + function onSubscribe() { + subscribed = true; + }; + + function messageArrived(response) { + messageReceived = true; + //reponse.invocationContext.onMessageArrived = null; + }; + + it('should create a new client', function() { + client = new settings.Paho.Client(testServer, testPort, testPath, "testclientid"); + client.onMessageArrived = messageArrived; + + expect(client).not.toBe(null); + expect(client.host).toBe(testServer); + expect(client.port).toBe(testPort); + expect(client.path).toBe(testPath); + }); + + it('should connect to a server', function() { + runs(function() { + client.connect({ + onSuccess: onConnect, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + + waitsFor(function() { + return connected; + }, "the client should connect", 10000); + + runs(function() { + expect(connected).toBe(true); + }); + }); + + it('should subscribe to a topic', function() { + runs(function() { + client.subscribe(topicPrefix + "/World", { + onSuccess: onSubscribe + }); + }); + + waitsFor(function() { + return subscribed; + }, "the client should subscribe", 2000); + + runs(function() { + expect(subscribed).toBe(true); + }); + }); + + it('should send and receive a message', function() { + runs(function() { + message = new settings.Paho.Message("Hello"); + message.destinationName = topicPrefix + "/World"; + client.send(message); + }) + + waitsFor(function() { + return messageReceived; + }, "the client should send and receive a message", 2000); + + runs(function() { + expect(messageReceived).toBe(true); + }) + }); }) diff --git a/src/test/client-harness.js b/src/test/client-harness.js index d259e12..943e8d4 100644 --- a/src/test/client-harness.js +++ b/src/test/client-harness.js @@ -1,126 +1,103 @@ -global.window = global; +global.self = global -var WebSocketClient = require('websocket').client; +var WebSocketClient = require('websocket').client var Paho = require('../paho-mqtt') +require('dotenv').config() + +global.WebSocket = function(wsurl, protocol) { + var ws = new WebSocketClient() + var connection + var obj = { + send: function(msg) { + var nodeBuf = new Buffer(new Uint8Array(msg)) + connection.send(nodeBuf) + }, + get readyState() { + return ws.readyState; + } + }; + ws.binaryType = 'arraybuffer'; -global.WebSocket = function(wsurl,protocol) { - var ws = new WebSocketClient(); - var connection; - var obj = { - send: function(msg) { - var nodeBuf = new Buffer(new Uint8Array(msg)); - connection.send(nodeBuf); - }, - get readyState() { return ws.readyState; } - }; - - ws.binaryType = 'arraybuffer'; - - ws.on("connect", function(conn) { - connection = conn; - conn.on("error", function (error) { - console.log("socket error ",error); - if (obj.onerror) { - obj.onerror(); - } - }); + ws.on("connect", function(conn) { + connection = conn; + conn.on("error", function(error) { + console.log("socket error ", error); + if (obj.onerror) { + obj.onerror(); + } + }); - conn.on("close", function(reasonCode, description) { - console.log("socket closed ",description); - }) + conn.on("close", function(reasonCode, description) { + console.log("socket closed ", description); + }) - conn.on("message", function (message) { - if (message.type === "binary") { - if (obj.onmessage) { - obj.onmessage({data:message.binaryData}); - } - } - }); - if (obj.onopen) { - obj.onopen(); + conn.on("message", function(message) { + if (message.type === "binary") { + if (obj.onmessage) { + obj.onmessage({ + data: message.binaryData + }); } + } }); - ws.on('connectFailed', function(error) { - console.log('Connect Error: ' + error.toString()); - if (obj.onerror) { - obj.onerror(error); - } - }); - ws.connect(wsurl, protocol); - return obj; + if (obj.onopen) { + obj.onopen(); + } + }); + ws.on('connectFailed', function(error) { + console.log('Connect Error: ' + error.toString()); + if (obj.onerror) { + obj.onerror(error); + } + }); + ws.connect(wsurl, protocol); + return obj; } -var LocalStorage = require('node-localstorage').LocalStorage; -global.localStorage = new LocalStorage('./persistence'); +var LocalStorage = require('node-localstorage').LocalStorage +global.localStorage = new LocalStorage('./persistence') var Paho = require('../paho-mqtt') -global.Paho = Paho; +global.Paho = Paho -function ensureValue(prop,value) { - if (prop == "" || prop[0] == "$") { - return value; - } - return prop; +function ensureValue(prop, value) { + if (prop === '' || prop[0] === '$') { + return value + } + return prop } function guid() { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) - .substring(1); + .substring(1) } return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); + s4() + '-' + s4() + s4() + s4() } -module.exports = { - server: ensureValue("${test.server}","iot.eclipse.org"), - port: parseInt(ensureValue("${test.server.port}","443")), - path: ensureValue("${test.server.path}","/ws"), - mqttVersion: parseInt(ensureValue("${test.server.mqttVersion}","3")), - interopServer: ensureValue("${test.interopServer}","iot.eclipse.org"), - interopPort: parseInt(ensureValue("${test.interopPort}","443")), - interopPath: ensureValue("${test.interopPath}","/ws"), - topicPrefix: "paho-mqtt-test-" + guid(), - Paho: Paho +function printConfig(settings) { + console.log(' - Eclipse Paho Javascript Client Test Harness Settings - ') + console.log('Server URI: ' + settings.server + ':' + settings.port + settings.path) + console.log('MQTT Version: ' + settings.mqttVersion) + console.log('Interop Server URI: ' + settings.interopServer + ':' + settings.interopPort + settings.interopPath) } -/* -var connection = { - "hostname" : "localhost", - "port" : "1883" -}; - -var broker = new Paho.Client(connection.hostname, Number(connection.port), "clientId"); -broker.onConnectionLost = onConnectionLost; -broker.onMessageArrived = onMessageArrived; -broker.connect({onSuccess:onConnect,onFailure : onConnectFailure}); -function onConnect() { - console.log("MQTT Broker Connected"); - console.log("broker = "); - console.log(this); - var topic = "/hello/world/#"; - broker.subscribe(topic); - - var staticTopic = "/hello/static"; - broker.subscribe(staticTopic); -}; -function onConnectFailure() { - console.log("connect failed"); +module.exports = { + server: ensureValue(process.env.TEST_SERVER, 'iot.eclipse.org'), + port: parseInt(ensureValue(process.env.TEST_SERVER_PORT, '443')), + path: ensureValue(process.env.TEST_SERVER_PATH, '/ws'), + mqttVersion: parseInt(ensureValue(process.env.TEST_SERVER_MQTTVER, '3')), + interopServer: ensureValue(process.env.TEST_INTEROPSERVER, 'iot.eclipse.org'), + interopPort: parseInt(ensureValue(process.env.TEST_INTEROPPORT, '443')), + interopPath: ensureValue(process.env.TEST_INTEROPPATH, '/ws'), + useSSL: ensureValue((process.env.TEST_USE_SSL === 'true'), true), + topicPrefix: 'paho-mqtt-test-' + guid(), + Paho: Paho, + printConfig: printConfig } -function onConnectionLost(responseObject) { - console.log("onConnectionLost"); - if (responseObject.errorCode !== 0) - console.log("onConnectionLost:"+responseObject.errorMessage); -}; - -function onMessageArrived(msg) { - console.log("onMessageArrived: " + msg._getDestinationName()); - console.log("MSG : '" + msg._getPayloadString() + "'"); - console.log(msg); -}; -*/ diff --git a/src/test/client-uris-spec.js b/src/test/client-uris-spec.js index af9d1a9..326e901 100644 --- a/src/test/client-uris-spec.js +++ b/src/test/client-uris-spec.js @@ -5,76 +5,79 @@ var testServer = settings.server; var testPort = settings.port; var testPath = settings.path; var testMqttVersion = settings.mqttVersion; +var testUseSSL = settings.useSSL describe('client-uris', function() { - var client = this; - var connected = false; - var subscribed = false; - var disconnectError = null; - var messageReceived = false; - - function onConnect() { - console.log("connected"); - disconnectError = null; - connected = true; - }; - function onDisconnect(err) { - console.log("disconnected"); - disconnectError = err; - connected = false; - } - function onSubscribe() { - console.log("subscribed"); - subscribed = true; - }; - - function messageArrived(response) { - console.log("messageArrived"); - messageReceived = true; - //reponse.invocationContext.onMessageArrived = null; - }; - - it('should create a new client with a default path', function() { - client = new Paho.Client(testServer, testPort, "testclientid"); - - expect(client).not.toBe(null); - expect(client.host).toBe(testServer); - expect(client.port).toBe(testPort); - expect(client.path).toBe("/mqtt"); - - }); - - it('should create a new client with a path', function() { - client = new Paho.Client(testServer, testPort, testPath, "testclientid"); - - expect(client).not.toBe(null); - expect(client.host).toBe(testServer); - expect(client.port).toBe(testPort); - expect(client.path).toBe(testPath); - }); - - it('should create a new client with a uri', function() { - client = new Paho.Client("ws://"+testServer+":"+testPort+testPath, "testclientid"); - - expect(client).not.toBe(null); - expect(client.host).toBe(testServer); - expect(client.port).toBe(testPort); - expect(client.path).toBe(testPath); - }); - - it('should fail to create a new client with an invalid ws uri', function() { - client = null; - var error; - try { - client = new Paho.Client("http://example.com", "testclientid"); - } catch(err) { - error = err; - } - expect(client).toBe(null); - expect(error).not.toBe(null); - }); - - /* + var client = this; + var connected = false; + var subscribed = false; + var disconnectError = null; + var messageReceived = false; + + function onConnect() { + console.log("connected"); + disconnectError = null; + connected = true; + }; + + function onDisconnect(err) { + console.log("disconnected"); + disconnectError = err; + connected = false; + } + + function onSubscribe() { + console.log("subscribed"); + subscribed = true; + }; + + function messageArrived(response) { + console.log("messageArrived"); + messageReceived = true; + //reponse.invocationContext.onMessageArrived = null; + }; + + it('should create a new client with a default path', function() { + client = new Paho.Client(testServer, testPort, "testclientid"); + + expect(client).not.toBe(null); + expect(client.host).toBe(testServer); + expect(client.port).toBe(testPort); + expect(client.path).toBe("/mqtt"); + + }); + + it('should create a new client with a path', function() { + client = new Paho.Client(testServer, testPort, testPath, "testclientid"); + + expect(client).not.toBe(null); + expect(client.host).toBe(testServer); + expect(client.port).toBe(testPort); + expect(client.path).toBe(testPath); + }); + + it('should create a new client with a uri', function() { + client = new Paho.Client("ws://" + testServer + ":" + testPort + testPath, "testclientid"); + + expect(client).not.toBe(null); + expect(client.host).toBe(testServer); + expect(client.port).toBe(testPort); + expect(client.path).toBe(testPath); + }); + + it('should fail to create a new client with an invalid ws uri', function() { + client = null; + var error; + try { + client = new Paho.Client("http://example.com", "testclientid"); + } catch (err) { + error = err; + } + expect(client).toBe(null); + expect(error).not.toBe(null); + }); + + /* // We don't yet expose setting the path element with the arrays of hosts/ports // If you want a path other than /mqtt, you need to use the array of hosts-as-uris. // Leaving this test here to remember this fact in case we add an array of paths to connopts @@ -110,35 +113,40 @@ describe('client-uris', function() { }); */ - it('should connect and disconnect to a server using connectoptions hosts', function() { - client = new Paho.Client(testServer, testPort, "testclientid"); - expect(client).not.toBe(null); - - client.onMessageArrived = messageArrived; - client.onConnectionLost = onDisconnect; - - runs(function() { - client.connect({onSuccess:onConnect,hosts:["ws://"+testServer+":"+testPort+testPath],mqttVersion:testMqttVersion, useSSL: true}); - }); - - waitsFor(function() { - return connected; - }, "the client should connect", 10000); - - runs(function() { - expect(connected).toBe(true); - }); - runs(function() { - client.disconnect(); - }); - waitsFor(function() { - return !connected; - }, "the client should disconnect",1000); - runs(function() { - expect(connected).toBe(false); - expect(disconnectError).not.toBe(null); - expect(disconnectError.errorCode).toBe(0); - }); - }); + it('should connect and disconnect to a server using connectoptions hosts', function() { + client = new Paho.Client(testServer, testPort, "testclientid"); + expect(client).not.toBe(null); + + client.onMessageArrived = messageArrived; + client.onConnectionLost = onDisconnect; + + runs(function() { + client.connect({ + onSuccess: onConnect, + hosts: ["ws://" + testServer + ":" + testPort + testPath], + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }) + }); + + waitsFor(function() { + return connected; + }, "the client should connect", 10000); + + runs(function() { + expect(connected).toBe(true); + }); + runs(function() { + client.disconnect(); + }); + waitsFor(function() { + return !connected; + }, "the client should disconnect", 1000); + runs(function() { + expect(connected).toBe(false); + expect(disconnectError).not.toBe(null); + expect(disconnectError.errorCode).toBe(0); + }); + }); }) diff --git a/src/test/interops-spec.js b/src/test/interops-spec.js index 724c51f..2bff66e 100644 --- a/src/test/interops-spec.js +++ b/src/test/interops-spec.js @@ -5,370 +5,402 @@ var testServer = settings.interopServer; var testPort = settings.interopPort; var testPath = settings.interopPath; var topicPrefix = settings.topicPrefix; -var testMqttVersion = 4; +var testUseSSL = settings.useSSL +var testMqttVersion = settings.mqttVersion; -var genStr = function(str){ - var time = new Date(); - return str + '.' + time.getTime(); +var genStr = function(str) { + var time = new Date(); + return str + '.' + time.getTime(); }; var topics = ["TopicA", "TopicA/B", "Topic/C", "TopicA/C", "/TopicA"]; var wildtopics = ["TopicA/+", "+/C", "#", "/#", "/+", "+/+", "TopicA/#"]; -var nosubscribetopics = ["test/nosubscribe",]; +var nosubscribetopics = ["test/nosubscribe", ]; describe('InteropsTests', function() { - var clientId = this.description; - var client = null; - var failure = false; - var subscribed = false; - var disconnectError = null; - var disconnectErrorMsg = null; - - var subscribed = false; - var messageReceivedCount = 0; - var messagePublishedCount = 0; - var sendingComplete = false; - var receivingComplete = false; - - beforeEach(function(){ - failure = false; - subscribed = false; - disconnectError = null; - disconnectErrorMsg = null; - messageReceivedCount = 0 - messagePublishedCount = 0; - sendingComplete = false; - receivingComplete = false; - }); - - afterEach(function(){ - if (client !== null && client.isConnected()) { - client.disconnect(); - } - client = null; - }); - - var callbacks = { - onConnectionLost: function(err) { - console.log("connectionLost " + err.errorMessage); - }, - onMessageArrived: function(message) { - console.log("messageArrived %s %s %s %s", message.destinationName, message.payloadString, message.qos, message.retained); - messageReceivedCount++; - if (messageReceivedCount == 3) { - receivingComplete = true; - } - }, - onConnectSuccess: function(response) { - connected = true; - }, - onConnectFailure: function(err) { - console.log('Connect failed %s %s',err.errCode,err.errorMessage); - }, - onDisconnectSuccess: function(response) { - connected = false; - console.log("Disconnected from server") - }, - onDisconnectFailure: function(err) { - console.log('Disconnect failed %s %s',err.errCode,err.errorMessage); - }, - onMessageDelivered: function(reponse) { - messagePublishedCount++; - if (messagePublishedCount == 3) { - sendingComplete = true; - } - }, - onSubscribeSuccess: function() { - subscribed = true; - }, - }; - - it('should connect, disconnect, subscribe, publish and receive messages', function() { - client = new Paho.Client(testServer, testPort, testPath, "testclientid-js"); - client.onMessageArrived = callbacks.onMessageArrived; - client.onMessageDelivered = callbacks.onMessageDelivered; - - expect(client).not.toBe(null); - - runs(function() { - client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, useSSL: true}); - }); - waitsFor(function() { - return client.isConnected(); - }, "the client should connect", 5000); - runs(function() { - expect(client.isConnected()).toBe(true); - }); - - runs(function() { - client.disconnect(); - }); - waitsFor(function() { - return true; - }, "the client should disconnect", 5000); - runs(function() { - expect(client.isConnected()).toBe(false); - }); - - runs(function() { - client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, useSSL: true}); - }); - waitsFor(function() { - return client.isConnected(); - }, "the client should connect again", 5000); - runs(function() { - expect(client.isConnected()).toBe(true); - }); - - runs(function() { - client.subscribe(topicPrefix + topics[0], {qos:2, onSuccess: callbacks.onSubscribeSuccess}); - }); - waitsFor(function() { - return subscribed; - }, "the client should subscribe", 2000); - runs(function() { - expect(subscribed).toBe(true); - }); - - runs(function (){ - for (var i = 0; i < 3; i++) { - var message = new Paho.Message("qos " + i); - message.destinationName = topicPrefix + topics[0]; - message.qos=i; - client.send(message); - } - }); - waitsFor(function() { - return sendingComplete; - }, "the client should send 3 messages", 5000); - waitsFor(function() { - return receivingComplete; - }, "the client should receive 3 messages", 5000); - runs(function() { - expect(messagePublishedCount).toBe(3); - expect(messageReceivedCount).toBe(3) - }); - - runs(function() { - client.disconnect({onSuccess: callbacks.onDisconnectSuccess}); - }); - waitsFor(function() { - return connected; - }, "the client should disconnect", 5000); - runs(function() { - expect(client.isConnected()).toBe(false); - }); - }); - - it('should connect, attempt to connect again and fail', function() { - var exception = false; - client = new Paho.Client(testServer, testPort, testPath, "testclientid-js"); - expect(client).not.toBe(null); - - runs(function() { - client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, useSSL: true}); - }); - waitsFor(function() { - return client.isConnected(); - }, "the client should connect", 5000); - runs(function() { - expect(client.isConnected()).toBe(true); - }); - - runs(function() { - try { - client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, useSSL: true}); - } catch (e) { - console.log(e.message) - if (e.message == "AMQJS0011E Invalid state already connected.") { - exception = true - } - } - }); - runs(function() { - expect(exception).toBe(true); - }); - }); - - it('should connect successfully with a 0 length clientid with cleansession true', function() { - client = new Paho.Client(testServer, testPort, testPath, ""); - expect(client).not.toBe(null); - - runs(function() { - client.connect({cleanSession:true, onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, useSSL: true}); - }); - waitsFor(function() { - return client.isConnected(); - }, "the client should connect", 5000); - runs(function() { - expect(client.isConnected()).toBe(true); - }); - - runs(function() { - client.disconnect(); - }); - waitsFor(function() { - return true; - }, "the client should disconnect", 5000); - runs(function() { - expect(client.isConnected()).toBe(false); - }); - }); - - it('should fail to connect successfully with a 0 length clientid with cleansession false', function() { - var connectFail = false; - var failCallback = function(err) { - connectFail = true; - } - client = new Paho.Client(testServer, testPort, testPath, ""); - expect(client).not.toBe(null); - - runs(function() { - client.connect({cleanSession:false, onFailure:failCallback, mqttVersion:testMqttVersion, useSSL: true}); - }); - waitsFor(function() { - return connectFail - }, "the client should fail to connect", 5000); - runs(function() { - expect(client.isConnected()).toBe(false); - }); - }); - /* - it('should queue up messages on the server for offline clients', function() { - client = new Paho.Client(testServer, testPort, testPath, "testclientid-js"); - client.onMessageArrived = callbacks.onMessageArrived; - - expect(client).not.toBe(null); - - runs(function() { - client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, cleanSession:false}); - }); - waitsFor(function() { - return client.isConnected(); - }, "the client should connect", 5000); - runs(function() { - expect(client.isConnected()).toBe(true); - }); - - runs(function() { - client.subscribe(wildtopics[5], {qos:2, onSuccess: callbacks.onSubscribeSuccess}); - }); - waitsFor(function() { - return subscribed; - }, "the client should subscribe", 2000); - runs(function() { - expect(subscribed).toBe(true); - }); - - runs(function() { - client.disconnect(); - }); - waitsFor(function() { - return true; - }, "the client should disconnect", 5000); - runs(function() { - expect(client.isConnected()).toBe(false); - }); - - bClient = new Paho.Client(testServer, testPort, testPath, "testclientid-js-b"); - bClient.onMessageDelivered = callbacks.onMessageDelivered; - - runs(function() { - bClient.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, cleanSession:true}); - }); - waitsFor(function() { - return bClient.isConnected(); - }, "the client should connect again", 5000); - runs(function() { - expect(bClient.isConnected()).toBe(true); - }); - - runs(function (){ - for (var i = 0; i < 3; i++) { - var message = new Paho.Message("qos " + i); - message.destinationName = topics[i+1]; - message.qos=i; - bClient.send(message); - } - }); - waitsFor(function() { - return sendingComplete; - }, "the client should send 3 messages", 5000); - runs(function() { - expect(messagePublishedCount).toBe(3); - }); - - runs(function() { - bClient.disconnect({onSuccess: callbacks.onDisconnectSuccess}); - }); - waitsFor(function() { - return connected; - }, "the client should disconnect", 5000); - runs(function() { - expect(bClient.isConnected()).toBe(false); - }); - - runs(function() { - client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, cleanSession:false}); - }); - waitsFor(function() { - return client.isConnected(); - }, "the client should connect", 5000); - runs(function() { - expect(client.isConnected()).toBe(true); - }); - waitsFor(function() { - return (messageReceivedCount > 1); - }, "the client should receive 2/3 messages", 5000); - runs(function() { - expect(messageReceivedCount).toBeGreaterThan(1); - }); - runs(function() { - client.disconnect(); - }); - waitsFor(function() { - return true; - }, "the client should disconnect", 5000); - runs(function() { - expect(client.isConnected()).toBe(false); - }); - }); - - - // This test has been commented out as it is only valid for a messagesight - // server and behaviour differs between mqtt server implementations. - it('should get a return code for failure to subscribe', function() { - client = new Paho.Client(testServer, testPort, testPath, "testclientid-js"); - client.onMessageArrived = callbacks.onMessageArrived; - - var subFailed = false; - var failSubscribe = function(response) { - if (response.errorCode.get(0) == 0x80) { - subFailed = true; - } - } - - expect(client).not.toBe(null); - - runs(function() { - client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, cleanSession:true}); - }); - waitsFor(function() { - return client.isConnected(); - }, "the client should connect", 5000); - runs(function() { - expect(client.isConnected()).toBe(true); - }); - - runs(function() { - client.subscribe(nosubscribetopics[0], {qos:2, onFailure: failSubscribe}); - }); - waitsFor(function() { - return subFailed; - }, "the client should fail to subscribe", 2000); - runs(function() { - expect(subFailed).toBe(true); - }); - }); - */ + var clientId = this.description; + var client = null; + var failure = false; + var subscribed = false; + var disconnectError = null; + var disconnectErrorMsg = null; + + var subscribed = false; + var messageReceivedCount = 0; + var messagePublishedCount = 0; + var sendingComplete = false; + var receivingComplete = false; + + beforeEach(function() { + failure = false; + subscribed = false; + disconnectError = null; + disconnectErrorMsg = null; + messageReceivedCount = 0 + messagePublishedCount = 0; + sendingComplete = false; + receivingComplete = false; + }); + + afterEach(function() { + if (client !== null && client.isConnected()) { + client.disconnect(); + } + client = null; + }); + + var callbacks = { + onConnectionLost: function(err) { + console.log("connectionLost " + err.errorMessage); + }, + onMessageArrived: function(message) { + console.log("messageArrived %s %s %s %s", message.destinationName, message.payloadString, message.qos, message.retained); + messageReceivedCount++; + if (messageReceivedCount == 3) { + receivingComplete = true; + } + }, + onConnectSuccess: function(response) { + connected = true; + }, + onConnectFailure: function(err) { + console.log('Connect failed %s %s', err.errCode, err.errorMessage); + }, + onDisconnectSuccess: function(response) { + connected = false; + console.log("Disconnected from server") + }, + onDisconnectFailure: function(err) { + console.log('Disconnect failed %s %s', err.errCode, err.errorMessage); + }, + onMessageDelivered: function(reponse) { + messagePublishedCount++; + if (messagePublishedCount == 3) { + sendingComplete = true; + } + }, + onSubscribeSuccess: function() { + subscribed = true; + }, + }; + + it('should connect, disconnect, subscribe, publish and receive messages', function() { + client = new Paho.Client(testServer, testPort, testPath, "testclientid-js"); + client.onMessageArrived = callbacks.onMessageArrived; + client.onMessageDelivered = callbacks.onMessageDelivered; + + expect(client).not.toBe(null); + + runs(function() { + client.connect({ + onSuccess: callbacks.onConnectSuccess, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + waitsFor(function() { + return client.isConnected(); + }, "the client should connect", 5000); + runs(function() { + expect(client.isConnected()).toBe(true); + }); + + runs(function() { + client.disconnect(); + }); + waitsFor(function() { + return true; + }, "the client should disconnect", 5000); + runs(function() { + expect(client.isConnected()).toBe(false); + }); + + runs(function() { + client.connect({ + onSuccess: callbacks.onConnectSuccess, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + waitsFor(function() { + return client.isConnected(); + }, "the client should connect again", 5000); + runs(function() { + expect(client.isConnected()).toBe(true); + }); + + runs(function() { + client.subscribe(topicPrefix + topics[0], { + qos: 2, + onSuccess: callbacks.onSubscribeSuccess + }); + }); + waitsFor(function() { + return subscribed; + }, "the client should subscribe", 2000); + runs(function() { + expect(subscribed).toBe(true); + }); + + runs(function() { + for (var i = 0; i < 3; i++) { + var message = new Paho.Message("qos " + i); + message.destinationName = topicPrefix + topics[0]; + message.qos = i; + client.send(message); + } + }); + waitsFor(function() { + return sendingComplete; + }, "the client should send 3 messages", 5000); + waitsFor(function() { + return receivingComplete; + }, "the client should receive 3 messages", 5000); + runs(function() { + expect(messagePublishedCount).toBe(3); + expect(messageReceivedCount).toBe(3) + }); + + runs(function() { + client.disconnect({ + onSuccess: callbacks.onDisconnectSuccess + }); + }); + waitsFor(function() { + return connected; + }, "the client should disconnect", 5000); + runs(function() { + expect(client.isConnected()).toBe(false); + }); + }); + + it('should connect, attempt to connect again and fail', function() { + var exception = false; + client = new Paho.Client(testServer, testPort, testPath, "testclientid-js"); + expect(client).not.toBe(null); + + runs(function() { + client.connect({ + onSuccess: callbacks.onConnectSuccess, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + waitsFor(function() { + return client.isConnected(); + }, "the client should connect", 5000); + runs(function() { + expect(client.isConnected()).toBe(true); + }); + + runs(function() { + try { + client.connect({ + onSuccess: callbacks.onConnectSuccess, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + } catch (e) { + console.log(e.message) + if (e.message == "AMQJS0011E Invalid state already connected.") { + exception = true + } + } + }); + runs(function() { + expect(exception).toBe(true); + }); + }); + + it('should connect successfully with a 0 length clientid with cleansession true', function() { + client = new Paho.Client(testServer, testPort, testPath, ""); + expect(client).not.toBe(null); + + runs(function() { + client.connect({ + cleanSession: true, + onSuccess: callbacks.onConnectSuccess, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + waitsFor(function() { + return client.isConnected(); + }, "the client should connect", 5000); + runs(function() { + expect(client.isConnected()).toBe(true); + }); + + runs(function() { + client.disconnect(); + }); + waitsFor(function() { + return true; + }, "the client should disconnect", 5000); + runs(function() { + expect(client.isConnected()).toBe(false); + }); + }); + + it('should fail to connect successfully with a 0 length clientid with cleansession false', function() { + var connectFail = false; + var failCallback = function(err) { + connectFail = true; + } + client = new Paho.Client(testServer, testPort, testPath, ""); + expect(client).not.toBe(null); + + runs(function() { + client.connect({ + cleanSession: false, + onFailure: failCallback, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + }); + waitsFor(function() { + return connectFail + }, "the client should fail to connect", 5000); + runs(function() { + expect(client.isConnected()).toBe(false); + }); + }); + /* + it('should queue up messages on the server for offline clients', function() { + client = new Paho.Client(testServer, testPort, testPath, "testclientid-js"); + client.onMessageArrived = callbacks.onMessageArrived; + + expect(client).not.toBe(null); + + runs(function() { + client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, cleanSession:false}); + }); + waitsFor(function() { + return client.isConnected(); + }, "the client should connect", 5000); + runs(function() { + expect(client.isConnected()).toBe(true); + }); + + runs(function() { + client.subscribe(wildtopics[5], {qos:2, onSuccess: callbacks.onSubscribeSuccess}); + }); + waitsFor(function() { + return subscribed; + }, "the client should subscribe", 2000); + runs(function() { + expect(subscribed).toBe(true); + }); + + runs(function() { + client.disconnect(); + }); + waitsFor(function() { + return true; + }, "the client should disconnect", 5000); + runs(function() { + expect(client.isConnected()).toBe(false); + }); + + bClient = new Paho.Client(testServer, testPort, testPath, "testclientid-js-b"); + bClient.onMessageDelivered = callbacks.onMessageDelivered; + + runs(function() { + bClient.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, cleanSession:true}); + }); + waitsFor(function() { + return bClient.isConnected(); + }, "the client should connect again", 5000); + runs(function() { + expect(bClient.isConnected()).toBe(true); + }); + + runs(function (){ + for (var i = 0; i < 3; i++) { + var message = new Paho.Message("qos " + i); + message.destinationName = topics[i+1]; + message.qos=i; + bClient.send(message); + } + }); + waitsFor(function() { + return sendingComplete; + }, "the client should send 3 messages", 5000); + runs(function() { + expect(messagePublishedCount).toBe(3); + }); + + runs(function() { + bClient.disconnect({onSuccess: callbacks.onDisconnectSuccess}); + }); + waitsFor(function() { + return connected; + }, "the client should disconnect", 5000); + runs(function() { + expect(bClient.isConnected()).toBe(false); + }); + + runs(function() { + client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, cleanSession:false}); + }); + waitsFor(function() { + return client.isConnected(); + }, "the client should connect", 5000); + runs(function() { + expect(client.isConnected()).toBe(true); + }); + waitsFor(function() { + return (messageReceivedCount > 1); + }, "the client should receive 2/3 messages", 5000); + runs(function() { + expect(messageReceivedCount).toBeGreaterThan(1); + }); + runs(function() { + client.disconnect(); + }); + waitsFor(function() { + return true; + }, "the client should disconnect", 5000); + runs(function() { + expect(client.isConnected()).toBe(false); + }); + }); + + + // This test has been commented out as it is only valid for a messagesight + // server and behaviour differs between mqtt server implementations. + it('should get a return code for failure to subscribe', function() { + client = new Paho.Client(testServer, testPort, testPath, "testclientid-js"); + client.onMessageArrived = callbacks.onMessageArrived; + + var subFailed = false; + var failSubscribe = function(response) { + if (response.errorCode.get(0) == 0x80) { + subFailed = true; + } + } + + expect(client).not.toBe(null); + + runs(function() { + client.connect({onSuccess: callbacks.onConnectSuccess, mqttVersion:testMqttVersion, cleanSession:true}); + }); + waitsFor(function() { + return client.isConnected(); + }, "the client should connect", 5000); + runs(function() { + expect(client.isConnected()).toBe(true); + }); + + runs(function() { + client.subscribe(nosubscribetopics[0], {qos:2, onFailure: failSubscribe}); + }); + waitsFor(function() { + return subFailed; + }, "the client should fail to subscribe", 2000); + runs(function() { + expect(subFailed).toBe(true); + }); + }); + */ }) diff --git a/src/test/live-take-over-spec.js b/src/test/live-take-over-spec.js index b9e6d9f..fe11d90 100644 --- a/src/test/live-take-over-spec.js +++ b/src/test/live-take-over-spec.js @@ -6,242 +6,260 @@ var testPort = settings.port; var testPath = settings.path; var testMqttVersion = settings.mqttVersion; var topicPrefix = settings.topicPrefix; +var testUseSSL = settings.useSSL describe('LiveTakeOver', function() { - //************************************************************************* - // Client wrapper - define a client wrapper to ease testing - //************************************************************************* - var MqttClient= function(clientId){ - var client = new Paho.Client(testServer, testPort, testPath, clientId); - //states - var connected = false; - var subscribed = false; - var messageReceived = false; - var messageDelivered = false; - var receivedMessage = null; - - this.states={connected:connected}; - - //reset all states - this.resetStates=function(){ - connected = false; - subscribed = false; - messageReceived = false; - messageDelivered = false; - receivedMessage = null; - }; - - //callbacks - var onConnect=function() { - console.log("%s connected",clientId); - connected = true; - }; - - var onDisconnect=function(response) { - console.log("%s disconnected",clientId); - connected = false; - }; - - var onSubscribe=function() { - console.log("%s subscribed",clientId); - subscribed = true; - }; - - var onUnsubscribe=function() { - console.log("%s unsubscribed",clientId); - subscribed = false; - }; - - var onMessageArrived=function(msg) { - console.log("%s received msg: %s",clientId,msg.payloadString); - messageReceived = true; - receivedMessage = msg; - }; - - var onMessageDelivered=function(msg){ - console.log("%s delivered message: %s",clientId,msg.payloadString); - messageDelivered=true; - } - - //set callbacks - client.onMessageArrived = onMessageArrived; - client.onConnectionLost = onDisconnect; - client.onMessageDelivered = onMessageDelivered; - - //functions - //connect and verify - this.connect=function(connectOptions){ - connectOptions = connectOptions || {}; - if(!connectOptions.hasOwnProperty("onSuccess")){ - connectOptions.onSuccess=onConnect; - connectOptions.mqttVersion=testMqttVersion; - connectOptions.useSSL = true; - } - runs(function() { - client.connect(connectOptions); - }); - - waitsFor(function() { - return connected; - }, "the client should connect", 10000); - - runs(function() { - expect(connected).toBe(true); - //reset state - connected=false; - }); - }; - - //disconnect and verify - this.disconnect=function(){ - runs(function() { - client.disconnect(); - }); - - waitsFor(function() { - return !connected; - }, "the client should disconnect", 10000); - - runs(function() { - expect(connected).not.toBe(true); - }); - }; - - //subscribe and verify - this.subscribe=function(topic,qos){ - runs(function() { - client.subscribe(topic, {qos:qos,onSuccess:onSubscribe}); - }); - - waitsFor(function() { - return subscribed; - }, "the client should subscribe", 2000); - - runs(function() { - expect(subscribed).toBe(true); - //reset state - subscribed=false; - }); - }; - - //unsubscribe and verify - this.unsubscribe=function(topic){ - runs(function() { - client.unsubscribe(topic, {onSuccess:onUnsubscribe}); - }); - - waitsFor(function() { - return !subscribed; - }, "the client should subscribe", 2000); - - runs(function() { - expect(subscribed).not.toBe(true); - }); - }; - - //publish and verify - this.publish=function(topic,qos,payload){ - runs(function() { - var message = new Paho.Message(payload); - message.destinationName = topic; - message.qos=qos; - client.send(message); - }) - - waitsFor(function() { - return messageDelivered; - }, "the client should delivered a message",10000); - - runs(function() { - //reset state - messageDelivered=false; - }); - }; - - - //verify no message received - this.receiveNone=function(){ - waits(2000); - runs(function() { - expect(messageReceived).toBe(false); - expect(receivedMessage).toBeNull(); - }); - }; - - //verify the receive message - this.receive=function(expectedTopic,publishedQoS,subscribedQoS,expectedPayload){ - - waitsFor(function() { - return messageReceived; - }, "the client should send and receive a message",10000); - - runs(function() { - expect(messageReceived).toBe(true); - expect(receivedMessage).not.toBeNull(); - expect(receivedMessage.qos).toBe(Math.min(publishedQoS,subscribedQoS)); - expect(receivedMessage.destinationName).toBe(expectedTopic); - if(typeof expectedPayload === "string"){ - expect(receivedMessage.payloadString).toEqual(expectedPayload); - }else{ - expect(receivedMessage.payloadBytes).toEqual(expectedPayload); - } - - //reset state after each publish - messageReceived=false; - receivedMessage=null; - }) - }; - }; - - //************************************************************************* - // Tests - //************************************************************************* - - it('should be taken over by another client for the actively doing work.', function() { - var clientId="TakeOverClient1"; - var testTopic=topicPrefix + "FirstClient/Topic"; - var subscribedQoS=2; - var publishQoS=1; - var payload="TakeOverPayload"; - - //will msg - var willMessage= new Paho.Message("will-payload"); - willMessage.destinationName = topicPrefix + "willTopic"; - willMessage.qos = 2; - willMessage.retained=true; - - var client1= new MqttClient(clientId); - client1.connect({cleanSession:false,willMessage:willMessage,mqttVersion:testMqttVersion, useSSL: true}); - - //subscribe - client1.subscribe(testTopic, subscribedQoS); - - //publish some messwage - for(var i=0;i<9;i++){ - client1.publish(testTopic,publishQoS,payload); - client1.receive(testTopic,publishQoS,subscribedQoS,payload); - } - - // Now lets take over the connection - // Create a second MQTT client connection with the same clientid. The - // server should spot this and kick the first client connection off. - var client2= new MqttClient(clientId); - client2.connect({cleanSession:false,willMessage:willMessage,mqttVersion:testMqttVersion, useSSL: true}); - - waitsFor(function() { - return !client1.states.connected; - }, "the previous client should be disconnected",10000); - - // We should have taken over the first Client's subscription... - //Now check we have grabbed his subscription by publishing. - client2.publish(testTopic,publishQoS,payload); - client2.receive(testTopic,publishQoS,subscribedQoS,payload); - - //disconnect - client2.disconnect(); - }); + //************************************************************************* + // Client wrapper - define a client wrapper to ease testing + //************************************************************************* + var MqttClient = function(clientId) { + var client = new Paho.Client(testServer, testPort, testPath, clientId); + //states + var connected = false; + var subscribed = false; + var messageReceived = false; + var messageDelivered = false; + var receivedMessage = null; + + this.states = { + connected: connected + }; + + //reset all states + this.resetStates = function() { + connected = false; + subscribed = false; + messageReceived = false; + messageDelivered = false; + receivedMessage = null; + }; + + //callbacks + var onConnect = function() { + console.log("%s connected", clientId); + connected = true; + }; + + var onDisconnect = function(response) { + console.log("%s disconnected", clientId); + connected = false; + }; + + var onSubscribe = function() { + console.log("%s subscribed", clientId); + subscribed = true; + }; + + var onUnsubscribe = function() { + console.log("%s unsubscribed", clientId); + subscribed = false; + }; + + var onMessageArrived = function(msg) { + console.log("%s received msg: %s", clientId, msg.payloadString); + messageReceived = true; + receivedMessage = msg; + }; + + var onMessageDelivered = function(msg) { + console.log("%s delivered message: %s", clientId, msg.payloadString); + messageDelivered = true; + } + + //set callbacks + client.onMessageArrived = onMessageArrived; + client.onConnectionLost = onDisconnect; + client.onMessageDelivered = onMessageDelivered; + + //functions + //connect and verify + this.connect = function(connectOptions) { + connectOptions = connectOptions || {}; + if (!connectOptions.hasOwnProperty("onSuccess")) { + connectOptions.onSuccess = onConnect; + connectOptions.mqttVersion = testMqttVersion; + connectOptions.useSSL = testUseSSL; + } + runs(function() { + client.connect(connectOptions); + }); + + waitsFor(function() { + return connected; + }, "the client should connect", 10000); + + runs(function() { + expect(connected).toBe(true); + //reset state + connected = false; + }); + }; + + //disconnect and verify + this.disconnect = function() { + runs(function() { + client.disconnect(); + }); + + waitsFor(function() { + return !connected; + }, "the client should disconnect", 10000); + + runs(function() { + expect(connected).not.toBe(true); + }); + }; + + //subscribe and verify + this.subscribe = function(topic, qos) { + runs(function() { + client.subscribe(topic, { + qos: qos, + onSuccess: onSubscribe + }); + }); + + waitsFor(function() { + return subscribed; + }, "the client should subscribe", 2000); + + runs(function() { + expect(subscribed).toBe(true); + //reset state + subscribed = false; + }); + }; + + //unsubscribe and verify + this.unsubscribe = function(topic) { + runs(function() { + client.unsubscribe(topic, { + onSuccess: onUnsubscribe + }); + }); + + waitsFor(function() { + return !subscribed; + }, "the client should subscribe", 2000); + + runs(function() { + expect(subscribed).not.toBe(true); + }); + }; + + //publish and verify + this.publish = function(topic, qos, payload) { + runs(function() { + var message = new Paho.Message(payload); + message.destinationName = topic; + message.qos = qos; + client.send(message); + }) + + waitsFor(function() { + return messageDelivered; + }, "the client should delivered a message", 10000); + + runs(function() { + //reset state + messageDelivered = false; + }); + }; + + + //verify no message received + this.receiveNone = function() { + waits(2000); + runs(function() { + expect(messageReceived).toBe(false); + expect(receivedMessage).toBeNull(); + }); + }; + + //verify the receive message + this.receive = function(expectedTopic, publishedQoS, subscribedQoS, expectedPayload) { + + waitsFor(function() { + return messageReceived; + }, "the client should send and receive a message", 10000); + + runs(function() { + expect(messageReceived).toBe(true); + expect(receivedMessage).not.toBeNull(); + expect(receivedMessage.qos).toBe(Math.min(publishedQoS, subscribedQoS)); + expect(receivedMessage.destinationName).toBe(expectedTopic); + if (typeof expectedPayload === "string") { + expect(receivedMessage.payloadString).toEqual(expectedPayload); + } else { + expect(receivedMessage.payloadBytes).toEqual(expectedPayload); + } + + //reset state after each publish + messageReceived = false; + receivedMessage = null; + }) + }; + }; + + //************************************************************************* + // Tests + //************************************************************************* + + it('should be taken over by another client for the actively doing work.', function() { + var clientId = "TakeOverClient1"; + var testTopic = topicPrefix + "FirstClient/Topic"; + var subscribedQoS = 2; + var publishQoS = 1; + var payload = "TakeOverPayload"; + + //will msg + var willMessage = new Paho.Message("will-payload"); + willMessage.destinationName = topicPrefix + "willTopic"; + willMessage.qos = 2; + willMessage.retained = true; + + var client1 = new MqttClient(clientId); + client1.connect({ + cleanSession: false, + willMessage: willMessage, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + + //subscribe + client1.subscribe(testTopic, subscribedQoS); + + //publish some messwage + for (var i = 0; i < 9; i++) { + client1.publish(testTopic, publishQoS, payload); + client1.receive(testTopic, publishQoS, subscribedQoS, payload); + } + + // Now lets take over the connection + // Create a second MQTT client connection with the same clientid. The + // server should spot this and kick the first client connection off. + var client2 = new MqttClient(clientId); + client2.connect({ + cleanSession: false, + willMessage: willMessage, + mqttVersion: testMqttVersion, + useSSL: testUseSSL + }); + + waitsFor(function() { + return !client1.states.connected; + }, "the previous client should be disconnected", 10000); + + // We should have taken over the first Client's subscription... + //Now check we have grabbed his subscription by publishing. + client2.publish(testTopic, publishQoS, payload); + client2.receive(testTopic, publishQoS, subscribedQoS, payload); + + //disconnect + client2.disconnect(); + }); }) diff --git a/src/test/send-receive-spec.js b/src/test/send-receive-spec.js index 457d275..fdf9fa2 100644 --- a/src/test/send-receive-spec.js +++ b/src/test/send-receive-spec.js @@ -6,354 +6,387 @@ var testPort = settings.port; var testPath = settings.path; var testMqttVersion = settings.mqttVersion; var topicPrefix = settings.topicPrefix; +var testUseSSL = settings.useSSL //define a default clientID -var clientId="testClient1"; +var clientId = "testClient1"; describe('SendReceive', function() { - //************************************************************************* - // Client wrapper - define a client wrapper to ease testing - //************************************************************************* - var MqttClient= function(clientId){ - var client = new Paho.Client(testServer, testPort, testPath, clientId); - //states - var connected = false; - var subscribed = false; - var messageReceived = false; - var messageDelivered = false; - var receivedMessage = null; - - //reset all states - this.resetStates=function(){ - connected = false; - subscribed = false; - messageReceived = false; - messageDelivered = false; - receivedMessage = null; - }; - - //callbacks - var onConnect=function() { - console.log("%s connected",clientId); - connected = true; - }; - - var onDisconnect=function(response) { - console.log("%s disconnected",clientId); - connected = false; - }; - - var onSubscribe=function() { - console.log("%s subscribed",clientId); - subscribed = true; - }; - - var onUnsubscribe=function() { - console.log("%s unsubscribed",clientId); - subscribed = false; - }; - - var onMessageArrived=function(msg) { - console.log("%s received msg: %s",clientId,msg.payloadString); - messageReceived = true; - receivedMessage = msg; - }; - - var onMessageDelivered=function(msg){ - console.log("%s delivered message: %s",clientId,msg.payloadString); - messageDelivered=true; - } - - //set callbacks - client.onMessageArrived = onMessageArrived; - client.onConnectionLost = onDisconnect; - client.onMessageDelivered = onMessageDelivered; - - //functions - //connect and verify - this.connect=function(connectOptions){ - connectOptions = connectOptions || {}; - if(!connectOptions.hasOwnProperty("onSuccess")){ - connectOptions.onSuccess=onConnect; - connectOptions.mqttVersion=testMqttVersion; - connectOptions.useSSL = true; - } - runs(function() { - client.connect(connectOptions); - }); - - waitsFor(function() { - return connected; - }, "the client should connect", 10000); - - runs(function() { - expect(connected).toBe(true); - //reset state - connected=false; - }); - }; - - //disconnect and verify - this.disconnect=function(){ - runs(function() { - client.disconnect(); - }); - - waitsFor(function() { - return !connected; - }, "the client should disconnect", 10000); - - runs(function() { - expect(connected).not.toBe(true); - }); - }; - - //subscribe and verify - this.subscribe=function(topic,qos){ - runs(function() { - client.subscribe(topic, {qos:qos,onSuccess:onSubscribe}); - }); - - waitsFor(function() { - return subscribed; - }, "the client should subscribe", 2000); - - runs(function() { - expect(subscribed).toBe(true); - //reset state - subscribed=false; - }); - }; - - //unsubscribe and verify - this.unsubscribe=function(topic){ - runs(function() { - client.unsubscribe(topic, {onSuccess:onUnsubscribe}); - }); - - waitsFor(function() { - return !subscribed; - }, "the client should subscribe", 2000); - - runs(function() { - expect(subscribed).not.toBe(true); - }); - }; - - //publish and verify - this.publish=function(topic,qos,payload){ - runs(function() { - var message = new Paho.Message(payload); - message.destinationName = topic; - message.qos=qos; - client.send(message); - }) - - waitsFor(function() { - return messageDelivered; - }, "the client should delivered a message",10000); - - runs(function() { - //reset state - messageDelivered=false; - }); - }; - - - //verify no message received - this.receiveNone=function(){ - waits(2000); - runs(function() { - expect(messageReceived).toBe(false); - expect(receivedMessage).toBeNull(); - }); - }; - - //verify the receive message - this.receive=function(expectedTopic,publishedQoS,subscribedQoS,expectedPayload){ - - waitsFor(function() { - return messageReceived; - }, "the client should send and receive a message",10000); - - runs(function() { - expect(messageReceived).toBe(true); - expect(receivedMessage).not.toBeNull(); - expect(receivedMessage.qos).toBe(Math.min(publishedQoS,subscribedQoS)); - expect(receivedMessage.destinationName).toBe(expectedTopic); - if(typeof expectedPayload === "string"){ - expect(receivedMessage.payloadString).toEqual(expectedPayload); - }else{ - expect(receivedMessage.payloadBytes).toEqual(expectedPayload); - } - - //reset state after each publish - messageReceived=false; - receivedMessage=null; - }) - }; - }; - - //************************************************************************* - // Tests - //************************************************************************* - - it('should connect to a server and disconnect from a server', function() { - var client= new MqttClient(clientId); - - //connect and verify - client.connect({mqttVersion:testMqttVersion, useSSL: true}); - - //disconnect and verify - client.disconnect(); - }); - - - it('should pub/sub using largish messages', function() { - var client= new MqttClient(clientId); - - //connect and verify - client.connect({mqttVersion:testMqttVersion, useSSL: true}); - - //subscribe and verify - var testTopic=topicPrefix + "pubsub/topic"; - var subscribedQoS=0; - client.subscribe(testTopic,subscribedQoS); - - //unsubscribe and verify - client.unsubscribe(testTopic); - - //subscribe again - client.subscribe(testTopic,subscribedQoS); - - //publish a large message to the topic and verify - var publishQoS=0; - var payload=""; - var largeSize=10000; - for(var i=0;i + +

+
+
+
Local Storage Warning
+
+

This page uses your browsers Local Storage functionality + to store MQTT messages whilst they are "inflight". This allows the Paho javascript client to ensure that QoS 1 and QoS 2 messages are successfully delivered even + if the network connection or browser fails. If you do not wish for the Local Storage to be used, please do not use this client, or only use QoS 0 to send and + receive messages. If you wish to see the data being stored for yourself, open up your developer console and look for the Local Storage section, + as messages are sent and received, you will see entries appearing and disappearing as the messages complete their QoS 1 and QoS 2 flows. + Messages are deleted as soon as they have completed their QoS flow.

+

More information about the Eclipse Privacy and cookie policy can be found Here.

+
+
+
+
+ +
@@ -112,9 +131,9 @@
-
+
- +
@@ -132,13 +151,19 @@
-
+
+
+
+ + +
+

@@ -328,6 +353,21 @@
+
+ +
+
+ +
+

+        
+
+
+
diff --git a/utility/style.css b/utility/style.css index 8ee2711..4e5db44 100644 --- a/utility/style.css +++ b/utility/style.css @@ -77,6 +77,10 @@ table.tableSection th, table.tableSection td { min-height: 250px; max-height: 250px; } +.fixed-height-panel-pre { + min-height: 250px; + max-height: 400px; +} .special-checkbox { display: block; diff --git a/utility/utility.js b/utility/utility.js index ecc33e5..4054a0a 100644 --- a/utility/utility.js +++ b/utility/utility.js @@ -20,27 +20,37 @@ This utility can be used to test the Eclipse Paho MQTT Javascript client. */ // Create a client instance -client = null; -connected = false; +var client = null; +var connected = false; +logMessage("INFO", "Starting Eclipse Paho JavaScript Utility."); + // Things to do as soon as the page loads -document.getElementById("clientIdInput").value = 'js-utility-' + makeid(); +document.getElementById("clientIdInput").value = "js-utility-" + makeid(); // called when the client connects function onConnect(context) { // Once a connection has been made, make a subscription and send a message. - console.log("Client Connected"); + var connectionString = context.invocationContext.host + ":" + context.invocationContext.port + context.invocationContext.path; + logMessage("INFO", "Connection Success ", "[URI: ", connectionString, ", ID: ", context.invocationContext.clientId, "]"); var statusSpan = document.getElementById("connectionStatus"); - statusSpan.innerHTML = "Connected to: " + context.invocationContext.host + ':' + context.invocationContext.port + context.invocationContext.path + ' as ' + context.invocationContext.clientId; + statusSpan.innerHTML = "Connected to: " + connectionString + " as " + context.invocationContext.clientId; connected = true; setFormEnabledState(true); +} + + +function onConnected(reconnect, uri) { + // Once a connection has been made, make a subscription and send a message. + logMessage("INFO", "Client Has now connected: [Reconnected: ", reconnect, ", URI: ", uri, "]"); + connected = true; } function onFail(context) { - console.log("Failed to connect"); + logMessage("ERROR", "Failed to connect. [Error Message: ", context.errorMessage, "]"); var statusSpan = document.getElementById("connectionStatus"); statusSpan.innerHTML = "Failed to connect: " + context.errorMessage; connected = false; @@ -50,49 +60,48 @@ function onFail(context) { // called when the client loses its connection function onConnectionLost(responseObject) { if (responseObject.errorCode !== 0) { - console.log("Connection Lost: " + responseObject.errorMessage); + logMessage("INFO", "Connection Lost. [Error Message: ", responseObject.errorMessage, "]"); } connected = false; } // called when a message arrives function onMessageArrived(message) { - console.log('Message Recieved: Topic: ', message.destinationName, '. Payload: ', message.payloadString, '. QoS: ', message.qos); - console.log(message); + logMessage("INFO", "Message Recieved: [Topic: ", message.destinationName, ", Payload: ", message.payloadString, ", QoS: ", message.qos, ", Retained: ", message.retained, ", Duplicate: ", message.duplicate, "]"); var messageTime = new Date().toISOString(); // Insert into History Table - var table = document.getElementById("incomingMessageTable").getElementsByTagName('tbody')[0]; + var table = document.getElementById("incomingMessageTable").getElementsByTagName("tbody")[0]; var row = table.insertRow(0); row.insertCell(0).innerHTML = message.destinationName; - row.insertCell(1).innerHTML = safe_tags_regex(message.payloadString); + row.insertCell(1).innerHTML = safeTagsRegex(message.payloadString); row.insertCell(2).innerHTML = messageTime; row.insertCell(3).innerHTML = message.qos; - if(!document.getElementById(message.destinationName)){ - var lastMessageTable = document.getElementById("lastMessageTable").getElementsByTagName('tbody')[0]; - var newlastMessageRow = lastMessageTable.insertRow(0); - newlastMessageRow.id = message.destinationName; - newlastMessageRow.insertCell(0).innerHTML = message.destinationName; - newlastMessageRow.insertCell(1).innerHTML = safe_tags_regex(message.payloadString); - newlastMessageRow.insertCell(2).innerHTML = messageTime; - newlastMessageRow.insertCell(3).innerHTML = message.qos; + if (!document.getElementById(message.destinationName)) { + var lastMessageTable = document.getElementById("lastMessageTable").getElementsByTagName("tbody")[0]; + var newlastMessageRow = lastMessageTable.insertRow(0); + newlastMessageRow.id = message.destinationName; + newlastMessageRow.insertCell(0).innerHTML = message.destinationName; + newlastMessageRow.insertCell(1).innerHTML = safeTagsRegex(message.payloadString); + newlastMessageRow.insertCell(2).innerHTML = messageTime; + newlastMessageRow.insertCell(3).innerHTML = message.qos; } else { - // Update Last Message Table - var lastMessageRow = document.getElementById(message.destinationName); - lastMessageRow.id = message.destinationName; - lastMessageRow.cells[0].innerHTML = message.destinationName; - lastMessageRow.cells[1].innerHTML = safe_tags_regex(message.payloadString); - lastMessageRow.cells[2].innerHTML = messageTime; - lastMessageRow.cells[3].innerHTML = message.qos; + // Update Last Message Table + var lastMessageRow = document.getElementById(message.destinationName); + lastMessageRow.id = message.destinationName; + lastMessageRow.cells[0].innerHTML = message.destinationName; + lastMessageRow.cells[1].innerHTML = safeTagsRegex(message.payloadString); + lastMessageRow.cells[2].innerHTML = messageTime; + lastMessageRow.cells[3].innerHTML = message.qos; } } -function connectionToggle(){ +function connectionToggle() { - if(connected){ + if (connected) { disconnect(); } else { connect(); @@ -102,182 +111,201 @@ function connectionToggle(){ } -function connect(){ - var hostname = document.getElementById("hostInput").value; - var port = document.getElementById("portInput").value; - var clientId = document.getElementById("clientIdInput").value; - - var path = document.getElementById("pathInput").value; - var user = document.getElementById("userInput").value; - var pass = document.getElementById("passInput").value; - var keepAlive = Number(document.getElementById("keepAliveInput").value); - var timeout = Number(document.getElementById("timeoutInput").value); - var tls = document.getElementById("tlsInput").checked; - var cleanSession = document.getElementById("cleanSessionInput").checked; - var lastWillTopic = document.getElementById("lwtInput").value; - var lastWillQos = Number(document.getElementById("lwQosInput").value); - var lastWillRetain = document.getElementById("lwRetainInput").checked; - var lastWillMessage = document.getElementById("lwMInput").value; - - - if(path.length > 0){ - client = new Paho.MQTT.Client(hostname, Number(port), path, clientId); - } else { - client = new Paho.MQTT.Client(hostname, Number(port), clientId); - } - console.info('Connecting to Server: Hostname: ', hostname, '. Port: ', port, '. Path: ', client.path, '. Client ID: ', clientId); - - // set callback handlers - client.onConnectionLost = onConnectionLost; - client.onMessageArrived = onMessageArrived; - - - var options = { - invocationContext: {host : hostname, port: port, path: client.path, clientId: clientId}, - timeout: timeout, - keepAliveInterval:keepAlive, - cleanSession: cleanSession, - useSSL: tls, - onSuccess: onConnect, - onFailure: onFail - }; - - - - if(user.length > 0){ - options.userName = user; - } - - if(pass.length > 0){ - options.password = pass; - } - - if(lastWillTopic.length > 0){ - var lastWillMessage = new Paho.MQTT.Message(lastWillMessage); - lastWillMessage.destinationName = lastWillTopic; - lastWillMessage.qos = lastWillQos; - lastWillMessage.retained = lastWillRetain; - options.willMessage = lastWillMessage; - } - - // connect the client - client.connect(options); - var statusSpan = document.getElementById("connectionStatus"); - statusSpan.innerHTML = 'Connecting...'; +function connect() { + var hostname = document.getElementById("hostInput").value; + var port = document.getElementById("portInput").value; + var clientId = document.getElementById("clientIdInput").value; + + var path = document.getElementById("pathInput").value; + var user = document.getElementById("userInput").value; + var pass = document.getElementById("passInput").value; + var keepAlive = Number(document.getElementById("keepAliveInput").value); + var timeout = Number(document.getElementById("timeoutInput").value); + var tls = document.getElementById("tlsInput").checked; + var automaticReconnect = document.getElementById("automaticReconnectInput").checked; + var cleanSession = document.getElementById("cleanSessionInput").checked; + var lastWillTopic = document.getElementById("lwtInput").value; + var lastWillQos = Number(document.getElementById("lwQosInput").value); + var lastWillRetain = document.getElementById("lwRetainInput").checked; + var lastWillMessageVal = document.getElementById("lwMInput").value; + + + if (path.length > 0) { + client = new Paho.Client(hostname, Number(port), path, clientId); + } else { + client = new Paho.Client(hostname, Number(port), clientId); + } + logMessage("INFO", "Connecting to Server: [Host: ", hostname, ", Port: ", port, ", Path: ", client.path, ", ID: ", clientId, "]"); + + // set callback handlers + client.onConnectionLost = onConnectionLost; + client.onMessageArrived = onMessageArrived; + client.onConnected = onConnected; + + + var options = { + invocationContext: { host: hostname, port: port, path: client.path, clientId: clientId }, + timeout: timeout, + keepAliveInterval: keepAlive, + cleanSession: cleanSession, + useSSL: tls, + reconnect: automaticReconnect, + onSuccess: onConnect, + onFailure: onFail + }; + + + + if (user.length > 0) { + options.userName = user; + } + + if (pass.length > 0) { + options.password = pass; + } + + if (lastWillTopic.length > 0) { + var lastWillMessage = new Paho.Message(lastWillMessageVal); + lastWillMessage.destinationName = lastWillTopic; + lastWillMessage.qos = lastWillQos; + lastWillMessage.retained = lastWillRetain; + options.willMessage = lastWillMessage; + } + + // connect the client + client.connect(options); + var statusSpan = document.getElementById("connectionStatus"); + statusSpan.innerHTML = "Connecting..."; } -function disconnect(){ - console.info('Disconnecting from Server'); - client.disconnect(); - var statusSpan = document.getElementById("connectionStatus"); - statusSpan.innerHTML = 'Connection - Disconnected.'; - connected = false; - setFormEnabledState(false); +function disconnect() { + logMessage("INFO", "Disconnecting from Server."); + client.disconnect(); + var statusSpan = document.getElementById("connectionStatus"); + statusSpan.innerHTML = "Connection - Disconnected."; + connected = false; + setFormEnabledState(false); } // Sets various form controls to either enabled or disabled -function setFormEnabledState(enabled){ - - // Connection Panel Elements - if(enabled){ - document.getElementById("clientConnectButton").innerHTML = "Disconnect"; - } else { - document.getElementById("clientConnectButton").innerHTML = "Connect"; - } - document.getElementById("hostInput").disabled = enabled; - document.getElementById("portInput").disabled = enabled; - document.getElementById("clientIdInput").disabled = enabled; - document.getElementById("pathInput").disabled = enabled; - document.getElementById("userInput").disabled = enabled; - document.getElementById("passInput").disabled = enabled; - document.getElementById("keepAliveInput").disabled = enabled; - document.getElementById("timeoutInput").disabled = enabled; - document.getElementById("tlsInput").disabled = enabled; - document.getElementById("cleanSessionInput").disabled = enabled; - document.getElementById("lwtInput").disabled = enabled; - document.getElementById("lwQosInput").disabled = enabled; - document.getElementById("lwRetainInput").disabled = enabled; - document.getElementById("lwMInput").disabled = enabled; - - // Publish Panel Elements - document.getElementById("publishTopicInput").disabled = !enabled; - document.getElementById("publishQosInput").disabled = !enabled; - document.getElementById("publishMessageInput").disabled = !enabled; - document.getElementById("publishButton").disabled = !enabled; - document.getElementById("publishRetainInput").disabled = !enabled; - - // Subscription Panel Elements - document.getElementById("subscribeTopicInput").disabled = !enabled; - document.getElementById("subscribeQosInput").disabled = !enabled; - document.getElementById("subscribeButton").disabled = !enabled; - document.getElementById("unsubscribeButton").disabled = !enabled; +function setFormEnabledState(enabled) { + + // Connection Panel Elements + if (enabled) { + document.getElementById("clientConnectButton").innerHTML = "Disconnect"; + } else { + document.getElementById("clientConnectButton").innerHTML = "Connect"; + } + document.getElementById("hostInput").disabled = enabled; + document.getElementById("portInput").disabled = enabled; + document.getElementById("clientIdInput").disabled = enabled; + document.getElementById("pathInput").disabled = enabled; + document.getElementById("userInput").disabled = enabled; + document.getElementById("passInput").disabled = enabled; + document.getElementById("keepAliveInput").disabled = enabled; + document.getElementById("timeoutInput").disabled = enabled; + document.getElementById("tlsInput").disabled = enabled; + document.getElementById("automaticReconnectInput").disabled = enabled; + document.getElementById("cleanSessionInput").disabled = enabled; + document.getElementById("lwtInput").disabled = enabled; + document.getElementById("lwQosInput").disabled = enabled; + document.getElementById("lwRetainInput").disabled = enabled; + document.getElementById("lwMInput").disabled = enabled; + + // Publish Panel Elements + document.getElementById("publishTopicInput").disabled = !enabled; + document.getElementById("publishQosInput").disabled = !enabled; + document.getElementById("publishMessageInput").disabled = !enabled; + document.getElementById("publishButton").disabled = !enabled; + document.getElementById("publishRetainInput").disabled = !enabled; + + // Subscription Panel Elements + document.getElementById("subscribeTopicInput").disabled = !enabled; + document.getElementById("subscribeQosInput").disabled = !enabled; + document.getElementById("subscribeButton").disabled = !enabled; + document.getElementById("unsubscribeButton").disabled = !enabled; } -function publish(){ - var topic = document.getElementById("publishTopicInput").value; - var qos = document.getElementById("publishQosInput").value; - var message = document.getElementById("publishMessageInput").value; - var retain = document.getElementById("publishRetainInput").checked - console.info('Publishing Message: Topic: ', topic, '. QoS: ' + qos + '. Message: ', message); - message = new Paho.MQTT.Message(message); - message.destinationName = topic; - message.qos = Number(qos); - message.retained = retain; - client.send(message); +function publish() { + var topic = document.getElementById("publishTopicInput").value; + var qos = document.getElementById("publishQosInput").value; + var message = document.getElementById("publishMessageInput").value; + var retain = document.getElementById("publishRetainInput").checked; + logMessage("INFO", "Publishing Message: [Topic: ", topic, ", Payload: ", message, ", QoS: ", qos, ", Retain: ", retain, "]"); + message = new Paho.Message(message); + message.destinationName = topic; + message.qos = Number(qos); + message.retained = retain; + client.send(message); } -function subscribe(){ - var topic = document.getElementById("subscribeTopicInput").value; - var qos = document.getElementById("subscribeQosInput").value; - console.info('Subscribing to: Topic: ', topic, '. QoS: ', qos); - client.subscribe(topic, {qos: Number(qos)}); +function subscribe() { + var topic = document.getElementById("subscribeTopicInput").value; + var qos = document.getElementById("subscribeQosInput").value; + logMessage("INFO", "Subscribing to: [Topic: ", topic, ", QoS: ", qos, "]"); + client.subscribe(topic, { qos: Number(qos) }); } -function unsubscribe(){ - var topic = document.getElementById("subscribeTopicInput").value; - console.info('Unsubscribing from ', topic); - client.unsubscribe(topic, { - onSuccess: unsubscribeSuccess, - onFailure: unsubscribeFailure, - invocationContext: {topic : topic} - }); +function unsubscribe() { + var topic = document.getElementById("subscribeTopicInput").value; + logMessage("INFO", "Unsubscribing: [Topic: ", topic, "]"); + client.unsubscribe(topic, { + onSuccess: unsubscribeSuccess, + onFailure: unsubscribeFailure, + invocationContext: { topic: topic } + }); } -function unsubscribeSuccess(context){ - console.info('Successfully unsubscribed from ', context.invocationContext.topic); +function unsubscribeSuccess(context) { + logMessage("INFO", "Unsubscribed. [Topic: ", context.invocationContext.topic, "]"); } -function unsubscribeFailure(context){ - console.info('Failed to unsubscribe from ', context.invocationContext.topic); +function unsubscribeFailure(context) { + logMessage("ERROR", "Failed to unsubscribe. [Topic: ", context.invocationContext.topic, ", Error: ", context.errorMessage, "]"); } -function clearHistory(){ - var table = document.getElementById("incomingMessageTable"); - //or use : var table = document.all.tableid; - for(var i = table.rows.length - 1; i > 0; i--) - { - table.deleteRow(i); - } +function clearHistory() { + var table = document.getElementById("incomingMessageTable"); + //or use : var table = document.all.tableid; + for (var i = table.rows.length - 1; i > 0; i--) { + table.deleteRow(i); + } } // Just in case someone sends html -function safe_tags_regex(str) { - return str.replace(/&/g, '&').replace(//g, '>'); +function safeTagsRegex(str) { + return str.replace(/&/g, "&").replace(//g, ">"); } -function makeid() -{ - var text = ""; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +function makeid() { + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for( var i=0; i < 5; i++ ) - text += possible.charAt(Math.floor(Math.random() * possible.length)); + for (var i = 0; i < 5; i++) + text += possible.charAt(Math.floor(Math.random() * possible.length)); - return text; + return text; } + +function logMessage(type, ...content) { + var consolePre = document.getElementById("consolePre"); + var date = new Date(); + var timeString = date.toUTCString(); + var logMessage = timeString + " - " + type + " - " + content.join(""); + consolePre.innerHTML += logMessage + "\n"; + if (type === "INFO") { + console.info(logMessage); + } else if (type === "ERROR") { + console.error(logMessage); + } else { + console.log(logMessage); + } +} +