├── .gitignore ├── .npmignore ├── README.md ├── package.json ├── plugin.xml └── www ├── mqtt.js └── paho-mqttws31.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Pure JavaScript MQTT client library. Wrapper for paho.js by [BeeToo](http://beetoo.me) ## 2 | 3 | ### Installation 4 | 5 | For Cordova CLI users: 6 | 7 | ``` 8 | cordova plugin add cordova-plugin-mqtt-pahojs 9 | ``` 10 | 11 | ### Usage example 12 | 13 | ``` 14 | new window.plugins.mqtt({ 15 | uri: MQTT_URI, 16 | keepAliveInterval: 120, 17 | clientId: MQTT_CLIENT_ID, 18 | reportConnectionStatus: false, 19 | reconnectDelay: [1000, 5000, 10000, -1] 20 | }); 21 | btc.connect(); 22 | 23 | var connection = new window.plugins.mqtt({ 24 | uri: 'ws://192.168.2.107:8080', 25 | keepAliveInterval: 120, 26 | clientId: 'BeeToo_MQTT', 27 | reportConnectionStatus: true, 28 | reconnectDelay: [1000, 5000, 10000, -1] //three tries to reconnect will take place. If -1 met, then reconnect process will stop. 29 | }); 30 | connection.on('connected', function () { 31 | connection.subscribe('/russia/moscow/beetoo_status', {qos: 2}); 32 | connection.publish('/russia/moscow/beetoo', 'hi, gyus!', {qos: 1, retained: true}); 33 | connection.on('/russia/moscow/beetoo_status', function (value) { 34 | console.log('Yay! Value is: ' + value); 35 | }); 36 | }); 37 | 38 | connection.connect(); 39 | ``` 40 | 41 | [The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-mqtt-pahojs", 3 | "cordova_name": "MQTT JavaScript Client", 4 | "version": "1.6.0", 5 | "description": "Pure JavaScript MQTT client library. Wrapper for paho.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "cordova-mqtt" 11 | ], 12 | "author": "Alexander Borovsky", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/estbeetoo/cordova-plugin-mqtt-pahojs.git" 17 | }, 18 | "platforms": [ 19 | "ios", 20 | "android", 21 | "wp8", 22 | "windows" 23 | ], 24 | "engines": [ 25 | { 26 | "name": "cordova", 27 | "version": ">=3.0.0" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | MQTT Paho 5 | Cordova MQTT Paho Plugin 6 | Apache 2.0 7 | cordova,mqtt,paho 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /www/mqtt.js: -------------------------------------------------------------------------------- 1 | var Paho = require('cordova-plugin-mqtt-pahojs.paho_mqttws31'); 2 | var EventEmitter = require('com.feedhenry.eventemitter.EventEmitter'); 3 | var SYSTEM_EVENTS = { 4 | 'status': true, 5 | 'disconnected': true, 6 | 'data': true, 7 | 'connecting': true, 8 | 'schedule_reconnect': true, 9 | 'connected': true, 10 | 'disconnect': true 11 | }; 12 | 13 | function MQTTClient(options) { 14 | EventEmitter(this); 15 | this.uri = options.uri || 'mqtt://' + options.host + ':' + (options.port || 8080); 16 | if (this.uri.substring(this.uri.length - 1) !== '/') this.uri += '/'; 17 | this.disconnectNormally = false; 18 | this.connected = false; 19 | this.reconnectTry = 0; 20 | this.clientId = options.clientId || options.clientID || options.client || "BeeToo"; 21 | this.userName = options.userName ? '' + options.userName : null; 22 | this.password = options.password ? '' + options.password : null; 23 | this.reportConnectionStatus = options.reportConnectionStatus || false; 24 | this.restoreConnection = true; 25 | this.reconnectDelay = options.reconnectDelay || [1000, 1000, 1000, 10000, 10000, 60000]; 26 | if (!Array.isArray(this.reconnectDelay)) 27 | this.reconnectDelay = [this.reconnectDelay]; 28 | this.reconnectDelayIdx = null; 29 | this.timeout = Math.round((options.timeout || 15000) / 1000); 30 | if (typeof options.reconnect !== 'undefined') this.restoreConnection = options.reconnect ? true : false; 31 | this._setupConnection(); 32 | } 33 | 34 | MQTTClient.prototype = new EventEmitter(); 35 | 36 | MQTTClient.prototype._showConnectionStatus = function(status) { 37 | if (status && this.reportConnectionStatus) 38 | window.plugins && window.plugins.toast && window.plugins.toast.showLongBottom(status); 39 | this.emit('status', status); 40 | }; 41 | 42 | MQTTClient.prototype._setupConnection = function() { 43 | if (this.connection) { 44 | try { 45 | this.connection.onConnectionLost = function() { 46 | console.debug('stale onConnectionLost called'); 47 | }; 48 | this.connection.onMessageArrived = function() { 49 | console.debug('stale onMessageArrived called'); 50 | }; 51 | this.connection.disconnect(); 52 | } catch (e) { 53 | } 54 | } 55 | this.connection = new Paho.MQTT.Client(this.uri, this.clientId); 56 | this.connection.onConnectionLost = this._connectionLost.bind(this); 57 | this.connection.onMessageArrived = this._onMessageArrived.bind(this); 58 | }; 59 | 60 | MQTTClient.prototype._connectionLost = function() { 61 | this.connected = false; 62 | this.reconnectTry = 0; 63 | this.emit('disconnected'); 64 | if (this.restoreConnection && !this.disconnectNormally) this.reconnect(); 65 | }; 66 | 67 | MQTTClient.prototype._onMessageArrived = function(msg) { 68 | this.emit('data', msg); 69 | if (!SYSTEM_EVENTS[msg.destinationName]) 70 | this.emit(msg.destinationName, msg.payloadString); 71 | }; 72 | 73 | MQTTClient.prototype._shouldReconnect = function() { 74 | if (this.reconnectDelayIdx !== null && this.reconnectDelay[this.reconnectDelayIdx] === -1) 75 | return false; 76 | return true; 77 | } 78 | 79 | MQTTClient.prototype._startReconnectCounter = function(callback) { 80 | this.reconnectCounterCounted = 0; 81 | this._showReconnectToast(this.reconnectDelay[this.reconnectDelayIdx]); 82 | this.reconnectCounter = setInterval(function() { 83 | this.reconnectCounterCounted += 1000; 84 | var time = (this.reconnectDelay[this.reconnectDelayIdx] - this.reconnectCounterCounted); 85 | if (time > 0) 86 | this._showReconnectToast(time); 87 | else { 88 | this.reconnectCounterCounted = 0; 89 | this._stopReconnectCounter(); 90 | callback && callback(); 91 | } 92 | }.bind(this), 1000); 93 | } 94 | 95 | MQTTClient.prototype._showReconnectToast = function(time /*in ms*/) { 96 | this._showConnectionStatus('Reconnecting in ' + Math.round(time / 1000) + 's'); 97 | } 98 | 99 | MQTTClient.prototype._incrementReconnectDelayIdx = function() { 100 | if (this.reconnectDelayIdx === null) 101 | return this.reconnectDelayIdx = 0; 102 | this.reconnectDelayIdx++; 103 | if (this.reconnectDelayIdx >= this.reconnectDelay.length) 104 | return this.reconnectDelayIdx = 0; 105 | return this.reconnectDelayIdx; 106 | } 107 | 108 | MQTTClient.prototype._stopReconnectCounter = function() { 109 | if (this.reconnectCounter) 110 | clearInterval(this.reconnectCounter); 111 | } 112 | 113 | MQTTClient.prototype.connect = function(reconnect) { 114 | if (!reconnect) 115 | this._markDisconnected(); 116 | this._stopReconnectCounter(); 117 | this._showConnectionStatus('Connecting...'); 118 | this.emit('connecting'); 119 | this.disconnectNormally = false; 120 | return new Promise(function(resolve, reject) { 121 | function connectionFailed(error) { 122 | this.connected = false; 123 | this._incrementReconnectDelayIdx(); 124 | if (this.restoreConnection && !this.disconnectNormally && this._shouldReconnect()) { 125 | this._startReconnectCounter(this.reconnect.bind(this)); 126 | this.emit('schedule_reconnect', this.reconnectDelay); 127 | } else { 128 | this.disconnect(); 129 | } 130 | reject && reject(error); 131 | }; 132 | 133 | function connectionSuccess() { 134 | if (this.disconnectNormally && this.connected === false) { 135 | return this.connection.disconnect(); 136 | } 137 | this.connected = true; 138 | this.reconnectTry = 0; 139 | this.reconnectDelayIdx = null; 140 | resolve && resolve(); 141 | this._showConnectionStatus('Connected'); 142 | this.emit('connected'); 143 | }; 144 | 145 | var connectionParams = { 146 | onSuccess: connectionSuccess.bind(this), 147 | onFailure: connectionFailed.bind(this) 148 | }; 149 | if (this.userName && this.userName.length) { 150 | connectionParams.userName = this.userName; 151 | connectionParams.password = this.password; 152 | } 153 | if (this.keepAliveInterval) connectionParams.keepAliveInterval = this.keepAliveInterval; 154 | if (this.timeout) connectionParams.timeout = this.timeout; 155 | try { 156 | this.connection.connect(connectionParams); 157 | } catch (error) { 158 | reject && reject(error); 159 | } 160 | }.bind(this)); 161 | }; 162 | 163 | MQTTClient.prototype.isConnected = function() { 164 | return this.connected; 165 | }; 166 | 167 | MQTTClient.prototype.reconnect = function() { 168 | function _connect() { 169 | this.connect(true).catch(function(e) { 170 | console.error('Error connecting, cause: ' + (e ? (e.message || e.errorMessage) : 'unknown reason')); 171 | }); 172 | } 173 | 174 | this.disconnectNormally = false; 175 | if (this.connected) { 176 | try { 177 | this.disconnect(); 178 | } catch (e) { 179 | console.error('Error disconnecting, cause: ' + (e ? (e.message || e.errorMessage) : 'unknown reason')); 180 | 181 | } finally { 182 | setTimeout(_connect.bind(this), 0); 183 | } 184 | } else { 185 | this._setupConnection(); 186 | _connect.apply(this); 187 | } 188 | }; 189 | 190 | MQTTClient.prototype._markDisconnected = function() { 191 | this.reconnectDelayIdx = null; 192 | this.disconnectNormally = true; 193 | this.reconnectTry = 0; 194 | this.connected = false; 195 | } 196 | 197 | MQTTClient.prototype.disconnect = function() { 198 | this._stopReconnectCounter(); 199 | this.emit('disconnect'); 200 | this._markDisconnected(); 201 | try { 202 | this.connection.disconnect(); 203 | } catch (e) { 204 | } 205 | this._showConnectionStatus('Disconnected'); 206 | }; 207 | 208 | MQTTClient.prototype.publish = function(topic, payload, options) { 209 | if (!options) options = {}; 210 | if (typeof options === undefined && typeof topic === 'undefined') { 211 | options = payload; 212 | topic = options.topic; 213 | payload = options.payload; 214 | } 215 | const message = new Paho.MQTT.Message(payload); 216 | message.destinationName = topic; 217 | if (typeof options.qos !== undefined) { 218 | const qos = parseInt(options.qos); 219 | if (!isNaN(qos)) message.qos = qos; 220 | } 221 | if (typeof options.retained !== undefined) message.retained = options.retained ? true : false; 222 | else if (typeof options.retain !== undefined) message.retain = options.retain ? true : false; 223 | message.onFailure = function() { 224 | this._showConnectionStatus('Sent error'); 225 | }.bind(this); 226 | this.connection.send(message); 227 | }; 228 | 229 | MQTTClient.prototype.subscribe = function(filter, options) { 230 | this.connection.subscribe(filter, options); 231 | } 232 | MQTTClient.prototype.unsubscribe = function(filter, options) { 233 | this.connection.unsubscribe(filter, options); 234 | } 235 | 236 | module.exports = MQTTClient; 237 | -------------------------------------------------------------------------------- /www/paho-mqttws31.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Eclipse Distribution License v1.0 which accompany this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * and the Eclipse Distribution License is available at 11 | * http://www.eclipse.org/org/documents/edl-v10.php. 12 | * 13 | * Contributors: 14 | * Andrew Banks - initial API and implementation and initial documentation 15 | *******************************************************************************/ 16 | 17 | 18 | // Only expose a single object name in the global namespace. 19 | // Everything must go through this module. Global Paho.MQTT module 20 | // only has a single public function, client, which returns 21 | // a Paho.MQTT client object given connection details. 22 | 23 | /** 24 | * Send and receive messages using web browsers. 25 | *

26 | * This programming interface lets a JavaScript client application use the MQTT V3.1 or 27 | * V3.1.1 protocol to connect to an MQTT-supporting messaging server. 28 | * 29 | * The function supported includes: 30 | *

    31 | *
  1. Connecting to and disconnecting from a server. The server is identified by its host name and port number. 32 | *
  2. Specifying options that relate to the communications link with the server, 33 | * for example the frequency of keep-alive heartbeats, and whether SSL/TLS is required. 34 | *
  3. Subscribing to and receiving messages from MQTT Topics. 35 | *
  4. Publishing messages to MQTT Topics. 36 | *
37 | *

38 | * The API consists of two main objects: 39 | *

40 | *
{@link Paho.MQTT.Client}
41 | *
This contains methods that provide the functionality of the API, 42 | * including provision of callbacks that notify the application when a message 43 | * arrives from or is delivered to the messaging server, 44 | * or when the status of its connection to the messaging server changes.
45 | *
{@link Paho.MQTT.Message}
46 | *
This encapsulates the payload of the message along with various attributes 47 | * associated with its delivery, in particular the destination to which it has 48 | * been (or is about to be) sent.
49 | *
50 | *

51 | * The programming interface validates parameters passed to it, and will throw 52 | * an Error containing an error message intended for developer use, if it detects 53 | * an error with any parameter. 54 | *

55 | * Example: 56 | * 57 | *

  58 |  client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId");
  59 |  client.onConnectionLost = onConnectionLost;
  60 |  client.onMessageArrived = onMessageArrived;
  61 |  client.connect({onSuccess:onConnect});
  62 | 
  63 |  function onConnect() {
  64 |   // Once a connection has been made, make a subscription and send a message.
  65 |   console.log("onConnect");
  66 |   client.subscribe("/World");
  67 |   message = new Paho.MQTT.Message("Hello");
  68 |   message.destinationName = "/World";
  69 |   client.send(message);
  70 | };
  71 |  function onConnectionLost(responseObject) {
  72 |   if (responseObject.errorCode !== 0)
  73 | 	console.log("onConnectionLost:"+responseObject.errorMessage);
  74 | };
  75 |  function onMessageArrived(message) {
  76 |   console.log("onMessageArrived:"+message.payloadString);
  77 |   client.disconnect();
  78 | };
  79 |  * 
80 | * @namespace Paho.MQTT 81 | */ 82 | 83 | if (typeof Paho === "undefined") { 84 | Paho = {}; 85 | } 86 | 87 | Paho.MQTT = (function(global) { 88 | 89 | // Private variables below, these are only visible inside the function closure 90 | // which is used to define the module. 91 | 92 | var version = "@VERSION@"; 93 | var buildLevel = "@BUILDLEVEL@"; 94 | 95 | /** 96 | * Unique message type identifiers, with associated 97 | * associated integer values. 98 | * @private 99 | */ 100 | var MESSAGE_TYPE = { 101 | CONNECT: 1, 102 | CONNACK: 2, 103 | PUBLISH: 3, 104 | PUBACK: 4, 105 | PUBREC: 5, 106 | PUBREL: 6, 107 | PUBCOMP: 7, 108 | SUBSCRIBE: 8, 109 | SUBACK: 9, 110 | UNSUBSCRIBE: 10, 111 | UNSUBACK: 11, 112 | PINGREQ: 12, 113 | PINGRESP: 13, 114 | DISCONNECT: 14 115 | }; 116 | 117 | // Collection of utility methods used to simplify module code 118 | // and promote the DRY pattern. 119 | 120 | /** 121 | * Validate an object's parameter names to ensure they 122 | * match a list of expected variables name for this option 123 | * type. Used to ensure option object passed into the API don't 124 | * contain erroneous parameters. 125 | * @param {Object} obj - User options object 126 | * @param {Object} keys - valid keys and types that may exist in obj. 127 | * @throws {Error} Invalid option parameter found. 128 | * @private 129 | */ 130 | var validate = function(obj, keys) { 131 | for(var key in obj) { 132 | if (obj.hasOwnProperty(key)) { 133 | if (keys.hasOwnProperty(key)) { 134 | if (typeof obj[key] !== keys[key]) 135 | throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key])); 136 | } else { 137 | var errorStr = "Unknown property, " + key + ". Valid properties are:"; 138 | for(var key in keys) 139 | if (keys.hasOwnProperty(key)) 140 | errorStr = errorStr + " " + key; 141 | throw new Error(errorStr); 142 | } 143 | } 144 | } 145 | }; 146 | 147 | /** 148 | * Return a new function which runs the user function bound 149 | * to a fixed scope. 150 | * @param {function} User function 151 | * @param {object} Function scope 152 | * @return {function} User function bound to another scope 153 | * @private 154 | */ 155 | var scope = function(f, scope) { 156 | return function() { 157 | return f.apply(scope, arguments); 158 | }; 159 | }; 160 | 161 | /** 162 | * Unique message type identifiers, with associated 163 | * associated integer values. 164 | * @private 165 | */ 166 | var ERROR = { 167 | OK: {code: 0, text: "AMQJSC0000I OK."}, 168 | CONNECT_TIMEOUT: {code: 1, text: "AMQJSC0001E Connect timed out."}, 169 | SUBSCRIBE_TIMEOUT: {code: 2, text: "AMQJS0002E Subscribe timed out."}, 170 | UNSUBSCRIBE_TIMEOUT: {code: 3, text: "AMQJS0003E Unsubscribe timed out."}, 171 | PING_TIMEOUT: {code: 4, text: "AMQJS0004E Ping timed out."}, 172 | INTERNAL_ERROR: {code: 5, text: "AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"}, 173 | CONNACK_RETURNCODE: {code: 6, text: "AMQJS0006E Bad Connack return code:{0} {1}."}, 174 | SOCKET_ERROR: {code: 7, text: "AMQJS0007E Socket error:{0}."}, 175 | SOCKET_CLOSE: {code: 8, text: "AMQJS0008I Socket closed."}, 176 | MALFORMED_UTF: {code: 9, text: "AMQJS0009E Malformed UTF data:{0} {1} {2}."}, 177 | UNSUPPORTED: {code: 10, text: "AMQJS0010E {0} is not supported by this browser."}, 178 | INVALID_STATE: {code: 11, text: "AMQJS0011E Invalid state {0}."}, 179 | INVALID_TYPE: {code: 12, text: "AMQJS0012E Invalid type {0} for {1}."}, 180 | INVALID_ARGUMENT: {code: 13, text: "AMQJS0013E Invalid argument {0} for {1}."}, 181 | UNSUPPORTED_OPERATION: {code: 14, text: "AMQJS0014E Unsupported operation."}, 182 | INVALID_STORED_DATA: {code: 15, text: "AMQJS0015E Invalid data in local storage key={0} value={1}."}, 183 | INVALID_MQTT_MESSAGE_TYPE: {code: 16, text: "AMQJS0016E Invalid MQTT message type {0}."}, 184 | MALFORMED_UNICODE: {code:17, text:"AMQJS0017E Malformed Unicode string:{0} {1}."} 185 | }; 186 | 187 | /** CONNACK RC Meaning. */ 188 | var CONNACK_RC = { 189 | 0: "Connection Accepted", 190 | 1: "Connection Refused: unacceptable protocol version", 191 | 2: "Connection Refused: identifier rejected", 192 | 3: "Connection Refused: server unavailable", 193 | 4: "Connection Refused: bad user name or password", 194 | 5: "Connection Refused: not authorized" 195 | }; 196 | 197 | /** 198 | * Format an error message text. 199 | * @private 200 | * @param {error} ERROR.KEY value above. 201 | * @param {substitutions} [array] substituted into the text. 202 | * @return the text with the substitutions made. 203 | */ 204 | var format = function(error, substitutions) { 205 | var text = error.text; 206 | if (substitutions) { 207 | var field, start; 208 | for(var i = 0; i < substitutions.length; i++) { 209 | field = "{" + i + "}"; 210 | start = text.indexOf(field); 211 | if (start > 0) { 212 | var part1 = text.substring(0, start); 213 | var part2 = text.substring(start + field.length); 214 | text = part1 + substitutions[i] + part2; 215 | } 216 | } 217 | } 218 | return text; 219 | }; 220 | 221 | //MQTT protocol and version 6 M Q I s d p 3 222 | var MqttProtoIdentifierv3 = [0x00, 0x06, 0x4d, 0x51, 0x49, 0x73, 0x64, 0x70, 0x03]; 223 | //MQTT proto/version for 311 4 M Q T T 4 224 | var MqttProtoIdentifierv4 = [0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, 0x04]; 225 | 226 | /** 227 | * Construct an MQTT wire protocol message. 228 | * @param type MQTT packet type. 229 | * @param options optional wire message attributes. 230 | * 231 | * Optional properties 232 | * 233 | * messageIdentifier: message ID in the range [0..65535] 234 | * payloadMessage: Application Message - PUBLISH only 235 | * connectStrings: array of 0 or more Strings to be put into the CONNECT payload 236 | * topics: array of strings (SUBSCRIBE, UNSUBSCRIBE) 237 | * requestQoS: array of QoS values [0..2] 238 | * 239 | * "Flag" properties 240 | * cleanSession: true if present / false if absent (CONNECT) 241 | * willMessage: true if present / false if absent (CONNECT) 242 | * isRetained: true if present / false if absent (CONNECT) 243 | * userName: true if present / false if absent (CONNECT) 244 | * password: true if present / false if absent (CONNECT) 245 | * keepAliveInterval: integer [0..65535] (CONNECT) 246 | * 247 | * @private 248 | * @ignore 249 | */ 250 | var WireMessage = function(type, options) { 251 | this.type = type; 252 | for(var name in options) { 253 | if (options.hasOwnProperty(name)) { 254 | this[name] = options[name]; 255 | } 256 | } 257 | }; 258 | 259 | WireMessage.prototype.encode = function() { 260 | // Compute the first byte of the fixed header 261 | var first = ((this.type & 0x0f) << 4); 262 | 263 | /* 264 | * Now calculate the length of the variable header + payload by adding up the lengths 265 | * of all the component parts 266 | */ 267 | 268 | var remLength = 0; 269 | var topicStrLength = new Array(); 270 | var destinationNameLength = 0; 271 | 272 | // if the message contains a messageIdentifier then we need two bytes for that 273 | if (this.messageIdentifier != undefined) 274 | remLength += 2; 275 | 276 | switch (this.type) { 277 | // If this a Connect then we need to include 12 bytes for its header 278 | case MESSAGE_TYPE.CONNECT: 279 | switch (this.mqttVersion) { 280 | case 3: 281 | remLength += MqttProtoIdentifierv3.length + 3; 282 | break; 283 | case 4: 284 | remLength += MqttProtoIdentifierv4.length + 3; 285 | break; 286 | } 287 | 288 | remLength += UTF8Length(this.clientId) + 2; 289 | if (this.willMessage != undefined) { 290 | remLength += UTF8Length(this.willMessage.destinationName) + 2; 291 | // Will message is always a string, sent as UTF-8 characters with a preceding length. 292 | var willMessagePayloadBytes = this.willMessage.payloadBytes; 293 | if (!(willMessagePayloadBytes instanceof Uint8Array)) 294 | willMessagePayloadBytes = new Uint8Array(payloadBytes); 295 | remLength += willMessagePayloadBytes.byteLength + 2; 296 | } 297 | if (this.userName != undefined) 298 | remLength += UTF8Length(this.userName) + 2; 299 | if (this.password != undefined) 300 | remLength += UTF8Length(this.password) + 2; 301 | break; 302 | 303 | // Subscribe, Unsubscribe can both contain topic strings 304 | case MESSAGE_TYPE.SUBSCRIBE: 305 | first |= 0x02; // Qos = 1; 306 | for(var i = 0; i < this.topics.length; i++) { 307 | topicStrLength[i] = UTF8Length(this.topics[i]); 308 | remLength += topicStrLength[i] + 2; 309 | } 310 | remLength += this.requestedQos.length; // 1 byte for each topic's Qos 311 | // QoS on Subscribe only 312 | break; 313 | 314 | case MESSAGE_TYPE.UNSUBSCRIBE: 315 | first |= 0x02; // Qos = 1; 316 | for(var i = 0; i < this.topics.length; i++) { 317 | topicStrLength[i] = UTF8Length(this.topics[i]); 318 | remLength += topicStrLength[i] + 2; 319 | } 320 | break; 321 | 322 | case MESSAGE_TYPE.PUBREL: 323 | first |= 0x02; // Qos = 1; 324 | break; 325 | 326 | case MESSAGE_TYPE.PUBLISH: 327 | if (this.payloadMessage.duplicate) first |= 0x08; 328 | first = first |= (this.payloadMessage.qos << 1); 329 | if (this.payloadMessage.retained) first |= 0x01; 330 | destinationNameLength = UTF8Length(this.payloadMessage.destinationName); 331 | remLength += destinationNameLength + 2; 332 | var payloadBytes = this.payloadMessage.payloadBytes; 333 | remLength += payloadBytes.byteLength; 334 | if (payloadBytes instanceof ArrayBuffer) 335 | payloadBytes = new Uint8Array(payloadBytes); 336 | else if (!(payloadBytes instanceof Uint8Array)) 337 | payloadBytes = new Uint8Array(payloadBytes.buffer); 338 | break; 339 | 340 | case MESSAGE_TYPE.DISCONNECT: 341 | break; 342 | 343 | default: 344 | ; 345 | } 346 | 347 | // Now we can allocate a buffer for the message 348 | 349 | var mbi = encodeMBI(remLength); // Convert the length to MQTT MBI format 350 | var pos = mbi.length + 1; // Offset of start of variable header 351 | var buffer = new ArrayBuffer(remLength + pos); 352 | var byteStream = new Uint8Array(buffer); // view it as a sequence of bytes 353 | 354 | //Write the fixed header into the buffer 355 | byteStream[0] = first; 356 | byteStream.set(mbi, 1); 357 | 358 | // If this is a PUBLISH then the variable header starts with a topic 359 | if (this.type == MESSAGE_TYPE.PUBLISH) 360 | pos = writeString(this.payloadMessage.destinationName, destinationNameLength, byteStream, pos); 361 | // If this is a CONNECT then the variable header contains the protocol name/version, flags and keepalive time 362 | 363 | else if (this.type == MESSAGE_TYPE.CONNECT) { 364 | switch (this.mqttVersion) { 365 | case 3: 366 | byteStream.set(MqttProtoIdentifierv3, pos); 367 | pos += MqttProtoIdentifierv3.length; 368 | break; 369 | case 4: 370 | byteStream.set(MqttProtoIdentifierv4, pos); 371 | pos += MqttProtoIdentifierv4.length; 372 | break; 373 | } 374 | var connectFlags = 0; 375 | if (this.cleanSession) 376 | connectFlags = 0x02; 377 | if (this.willMessage != undefined) { 378 | connectFlags |= 0x04; 379 | connectFlags |= (this.willMessage.qos << 3); 380 | if (this.willMessage.retained) { 381 | connectFlags |= 0x20; 382 | } 383 | } 384 | if (this.userName != undefined) 385 | connectFlags |= 0x80; 386 | if (this.password != undefined) 387 | connectFlags |= 0x40; 388 | byteStream[pos++] = connectFlags; 389 | pos = writeUint16(this.keepAliveInterval, byteStream, pos); 390 | } 391 | 392 | // Output the messageIdentifier - if there is one 393 | if (this.messageIdentifier != undefined) 394 | pos = writeUint16(this.messageIdentifier, byteStream, pos); 395 | 396 | switch (this.type) { 397 | case MESSAGE_TYPE.CONNECT: 398 | pos = writeString(this.clientId, UTF8Length(this.clientId), byteStream, pos); 399 | if (this.willMessage != undefined) { 400 | pos = writeString(this.willMessage.destinationName, UTF8Length(this.willMessage.destinationName), byteStream, pos); 401 | pos = writeUint16(willMessagePayloadBytes.byteLength, byteStream, pos); 402 | byteStream.set(willMessagePayloadBytes, pos); 403 | pos += willMessagePayloadBytes.byteLength; 404 | 405 | } 406 | if (this.userName != undefined) 407 | pos = writeString(this.userName, UTF8Length(this.userName), byteStream, pos); 408 | if (this.password != undefined) 409 | pos = writeString(this.password, UTF8Length(this.password), byteStream, pos); 410 | break; 411 | 412 | case MESSAGE_TYPE.PUBLISH: 413 | // PUBLISH has a text or binary payload, if text do not add a 2 byte length field, just the UTF characters. 414 | byteStream.set(payloadBytes, pos); 415 | 416 | break; 417 | 418 | // case MESSAGE_TYPE.PUBREC: 419 | // case MESSAGE_TYPE.PUBREL: 420 | // case MESSAGE_TYPE.PUBCOMP: 421 | // break; 422 | 423 | case MESSAGE_TYPE.SUBSCRIBE: 424 | // SUBSCRIBE has a list of topic strings and request QoS 425 | for(var i = 0; i < this.topics.length; i++) { 426 | pos = writeString(this.topics[i], topicStrLength[i], byteStream, pos); 427 | byteStream[pos++] = this.requestedQos[i]; 428 | } 429 | break; 430 | 431 | case MESSAGE_TYPE.UNSUBSCRIBE: 432 | // UNSUBSCRIBE has a list of topic strings 433 | for(var i = 0; i < this.topics.length; i++) 434 | pos = writeString(this.topics[i], topicStrLength[i], byteStream, pos); 435 | break; 436 | 437 | default: 438 | // Do nothing. 439 | } 440 | 441 | return buffer; 442 | } 443 | 444 | function decodeMessage(input, pos) { 445 | var startingPos = pos; 446 | var first = input[pos]; 447 | var type = first >> 4; 448 | var messageInfo = first &= 0x0f; 449 | pos += 1; 450 | 451 | 452 | // Decode the remaining length (MBI format) 453 | 454 | var digit; 455 | var remLength = 0; 456 | var multiplier = 1; 457 | do { 458 | if (pos == input.length) { 459 | return [null, startingPos]; 460 | } 461 | digit = input[pos++]; 462 | remLength += ((digit & 0x7F) * multiplier); 463 | multiplier *= 128; 464 | } while ((digit & 0x80) != 0); 465 | 466 | var endPos = pos + remLength; 467 | if (endPos > input.length) { 468 | return [null, startingPos]; 469 | } 470 | 471 | var wireMessage = new WireMessage(type); 472 | switch (type) { 473 | case MESSAGE_TYPE.CONNACK: 474 | var connectAcknowledgeFlags = input[pos++]; 475 | if (connectAcknowledgeFlags & 0x01) 476 | wireMessage.sessionPresent = true; 477 | wireMessage.returnCode = input[pos++]; 478 | break; 479 | 480 | case MESSAGE_TYPE.PUBLISH: 481 | var qos = (messageInfo >> 1) & 0x03; 482 | 483 | var len = readUint16(input, pos); 484 | pos += 2; 485 | var topicName = parseUTF8(input, pos, len); 486 | pos += len; 487 | // If QoS 1 or 2 there will be a messageIdentifier 488 | if (qos > 0) { 489 | wireMessage.messageIdentifier = readUint16(input, pos); 490 | pos += 2; 491 | } 492 | 493 | var message = new Paho.MQTT.Message(input.subarray(pos, endPos)); 494 | if ((messageInfo & 0x01) == 0x01) 495 | message.retained = true; 496 | if ((messageInfo & 0x08) == 0x08) 497 | message.duplicate = true; 498 | message.qos = qos; 499 | message.destinationName = topicName; 500 | wireMessage.payloadMessage = message; 501 | break; 502 | 503 | case MESSAGE_TYPE.PUBACK: 504 | case MESSAGE_TYPE.PUBREC: 505 | case MESSAGE_TYPE.PUBREL: 506 | case MESSAGE_TYPE.PUBCOMP: 507 | case MESSAGE_TYPE.UNSUBACK: 508 | wireMessage.messageIdentifier = readUint16(input, pos); 509 | break; 510 | 511 | case MESSAGE_TYPE.SUBACK: 512 | wireMessage.messageIdentifier = readUint16(input, pos); 513 | pos += 2; 514 | wireMessage.returnCode = input.subarray(pos, endPos); 515 | break; 516 | 517 | default: 518 | ; 519 | } 520 | 521 | return [wireMessage, endPos]; 522 | } 523 | 524 | function writeUint16(input, buffer, offset) { 525 | buffer[offset++] = input >> 8; //MSB 526 | buffer[offset++] = input % 256; //LSB 527 | return offset; 528 | } 529 | 530 | function writeString(input, utf8Length, buffer, offset) { 531 | offset = writeUint16(utf8Length, buffer, offset); 532 | stringToUTF8(input, buffer, offset); 533 | return offset + utf8Length; 534 | } 535 | 536 | function readUint16(buffer, offset) { 537 | return 256 * buffer[offset] + buffer[offset + 1]; 538 | } 539 | 540 | /** 541 | * Encodes an MQTT Multi-Byte Integer 542 | * @private 543 | */ 544 | function encodeMBI(number) { 545 | var output = new Array(1); 546 | var numBytes = 0; 547 | 548 | do { 549 | var digit = number % 128; 550 | number = number >> 7; 551 | if (number > 0) { 552 | digit |= 0x80; 553 | } 554 | output[numBytes++] = digit; 555 | } while ((number > 0) && (numBytes < 4)); 556 | 557 | return output; 558 | } 559 | 560 | /** 561 | * Takes a String and calculates its length in bytes when encoded in UTF8. 562 | * @private 563 | */ 564 | function UTF8Length(input) { 565 | var output = 0; 566 | for(var i = 0; i < input.length; i++) { 567 | var charCode = input.charCodeAt(i); 568 | if (charCode > 0x7FF) { 569 | // Surrogate pair means its a 4 byte character 570 | if (0xD800 <= charCode && charCode <= 0xDBFF) { 571 | i++; 572 | output++; 573 | } 574 | output += 3; 575 | } 576 | else if (charCode > 0x7F) 577 | output += 2; 578 | else 579 | output++; 580 | } 581 | return output; 582 | } 583 | 584 | /** 585 | * Takes a String and writes it into an array as UTF8 encoded bytes. 586 | * @private 587 | */ 588 | function stringToUTF8(input, output, start) { 589 | var pos = start; 590 | for(var i = 0; i < input.length; i++) { 591 | var charCode = input.charCodeAt(i); 592 | 593 | // Check for a surrogate pair. 594 | if (0xD800 <= charCode && charCode <= 0xDBFF) { 595 | var lowCharCode = input.charCodeAt(++i); 596 | if (isNaN(lowCharCode)) { 597 | throw new Error(format(ERROR.MALFORMED_UNICODE, [charCode, lowCharCode])); 598 | } 599 | charCode = ((charCode - 0xD800) << 10) + (lowCharCode - 0xDC00) + 0x10000; 600 | 601 | } 602 | 603 | if (charCode <= 0x7F) { 604 | output[pos++] = charCode; 605 | } else if (charCode <= 0x7FF) { 606 | output[pos++] = charCode >> 6 & 0x1F | 0xC0; 607 | output[pos++] = charCode & 0x3F | 0x80; 608 | } else if (charCode <= 0xFFFF) { 609 | output[pos++] = charCode >> 12 & 0x0F | 0xE0; 610 | output[pos++] = charCode >> 6 & 0x3F | 0x80; 611 | output[pos++] = charCode & 0x3F | 0x80; 612 | } else { 613 | output[pos++] = charCode >> 18 & 0x07 | 0xF0; 614 | output[pos++] = charCode >> 12 & 0x3F | 0x80; 615 | output[pos++] = charCode >> 6 & 0x3F | 0x80; 616 | output[pos++] = charCode & 0x3F | 0x80; 617 | } 618 | ; 619 | } 620 | return output; 621 | } 622 | 623 | function parseUTF8(input, offset, length) { 624 | var output = ""; 625 | var utf16; 626 | var pos = offset; 627 | 628 | while (pos < offset + length) { 629 | var byte1 = input[pos++]; 630 | if (byte1 < 128) 631 | utf16 = byte1; 632 | else { 633 | var byte2 = input[pos++] - 128; 634 | if (byte2 < 0) 635 | throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), ""])); 636 | if (byte1 < 0xE0) // 2 byte character 637 | utf16 = 64 * (byte1 - 0xC0) + byte2; 638 | else { 639 | var byte3 = input[pos++] - 128; 640 | if (byte3 < 0) 641 | throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16)])); 642 | if (byte1 < 0xF0) // 3 byte character 643 | utf16 = 4096 * (byte1 - 0xE0) + 64 * byte2 + byte3; 644 | else { 645 | var byte4 = input[pos++] - 128; 646 | if (byte4 < 0) 647 | throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); 648 | if (byte1 < 0xF8) // 4 byte character 649 | utf16 = 262144 * (byte1 - 0xF0) + 4096 * byte2 + 64 * byte3 + byte4; 650 | else // longer encodings are not supported 651 | throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); 652 | } 653 | } 654 | } 655 | 656 | if (utf16 > 0xFFFF) // 4 byte character - express as a surrogate pair 657 | { 658 | utf16 -= 0x10000; 659 | output += String.fromCharCode(0xD800 + (utf16 >> 10)); // lead character 660 | utf16 = 0xDC00 + (utf16 & 0x3FF); // trail character 661 | } 662 | output += String.fromCharCode(utf16); 663 | } 664 | return output; 665 | } 666 | 667 | /** 668 | * Repeat keepalive requests, monitor responses. 669 | * @ignore 670 | */ 671 | var Pinger = function(client, window, keepAliveInterval) { 672 | this._client = client; 673 | this._window = window; 674 | this._keepAliveInterval = keepAliveInterval * 1000; 675 | this.isReset = false; 676 | 677 | var pingReq = new WireMessage(MESSAGE_TYPE.PINGREQ).encode(); 678 | 679 | var doTimeout = function(pinger) { 680 | return function() { 681 | return doPing.apply(pinger); 682 | }; 683 | }; 684 | 685 | /** @ignore */ 686 | var doPing = function() { 687 | if (!this.isReset) { 688 | this._client._trace("Pinger.doPing", "Timed out"); 689 | this._client._disconnected(ERROR.PING_TIMEOUT.code, format(ERROR.PING_TIMEOUT)); 690 | } else { 691 | this.isReset = false; 692 | this._client._trace("Pinger.doPing", "send PINGREQ"); 693 | try { 694 | this._client.socket.send(pingReq); 695 | this.timeout = this._window.setTimeout(doTimeout(this), this._keepAliveInterval); 696 | } 697 | catch (e) { 698 | this._client._trace("Pinger.doPing", format(ERROR.SOCKET_ERROR, [e.message])); 699 | this._client._on_socket_error({data: e.message}); 700 | } 701 | } 702 | } 703 | 704 | this.reset = function() { 705 | this.isReset = true; 706 | this._window.clearTimeout(this.timeout); 707 | if (this._keepAliveInterval > 0) 708 | this.timeout = setTimeout(doTimeout(this), this._keepAliveInterval); 709 | } 710 | 711 | this.cancel = function() { 712 | this._window.clearTimeout(this.timeout); 713 | } 714 | }; 715 | 716 | /** 717 | * Monitor request completion. 718 | * @ignore 719 | */ 720 | var Timeout = function(client, window, timeoutSeconds, action, args) { 721 | this._window = window; 722 | if (!timeoutSeconds) 723 | timeoutSeconds = 30; 724 | 725 | var doTimeout = function(action, client, args) { 726 | return function() { 727 | return action.apply(client, args); 728 | }; 729 | }; 730 | this.timeout = setTimeout(doTimeout(action, client, args), timeoutSeconds * 1000); 731 | 732 | this.cancel = function() { 733 | this._window.clearTimeout(this.timeout); 734 | } 735 | }; 736 | 737 | /* 738 | * Internal implementation of the Websockets MQTT V3.1 client. 739 | * 740 | * @name Paho.MQTT.ClientImpl @constructor 741 | * @param {String} host the DNS nameof the webSocket host. 742 | * @param {Number} port the port number for that host. 743 | * @param {String} clientId the MQ client identifier. 744 | */ 745 | var ClientImpl = function(uri, host, port, path, clientId) { 746 | // Check dependencies are satisfied in this browser. 747 | if (!("WebSocket" in global && global["WebSocket"] !== null)) { 748 | throw new Error(format(ERROR.UNSUPPORTED, ["WebSocket"])); 749 | } 750 | if (!("localStorage" in global && global["localStorage"] !== null)) { 751 | throw new Error(format(ERROR.UNSUPPORTED, ["localStorage"])); 752 | } 753 | if (!("ArrayBuffer" in global && global["ArrayBuffer"] !== null)) { 754 | throw new Error(format(ERROR.UNSUPPORTED, ["ArrayBuffer"])); 755 | } 756 | this._trace("Paho.MQTT.Client", uri, host, port, path, clientId); 757 | 758 | this.host = host; 759 | this.port = port; 760 | this.path = path; 761 | this.uri = uri; 762 | this.clientId = clientId; 763 | 764 | // Local storagekeys are qualified with the following string. 765 | // The conditional inclusion of path in the key is for backward 766 | // compatibility to when the path was not configurable and assumed to 767 | // be /mqtt 768 | this._localKey = host + ":" + port + (path != "/mqtt" ? ":" + path : "") + ":" + clientId + ":"; 769 | 770 | // Create private instance-only message queue 771 | // Internal queue of messages to be sent, in sending order. 772 | this._msg_queue = []; 773 | 774 | // Messages we have sent and are expecting a response for, indexed by their respective message ids. 775 | this._sentMessages = {}; 776 | 777 | // Messages we have received and acknowleged and are expecting a confirm message for 778 | // indexed by their respective message ids. 779 | this._receivedMessages = {}; 780 | 781 | // Internal list of callbacks to be executed when messages 782 | // have been successfully sent over web socket, e.g. disconnect 783 | // when it doesn't have to wait for ACK, just message is dispatched. 784 | this._notify_msg_sent = {}; 785 | 786 | // Unique identifier for SEND messages, incrementing 787 | // counter as messages are sent. 788 | this._message_identifier = 1; 789 | 790 | // Used to determine the transmission sequence of stored sent messages. 791 | this._sequence = 0; 792 | 793 | 794 | // Load the local state, if any, from the saved version, only restore state relevant to this client. 795 | for(var key in localStorage) 796 | if (key.indexOf("Sent:" + this._localKey) == 0 797 | || key.indexOf("Received:" + this._localKey) == 0) 798 | this.restore(key); 799 | }; 800 | 801 | // Messaging Client public instance members. 802 | ClientImpl.prototype.host; 803 | ClientImpl.prototype.port; 804 | ClientImpl.prototype.path; 805 | ClientImpl.prototype.uri; 806 | ClientImpl.prototype.clientId; 807 | 808 | // Messaging Client private instance members. 809 | ClientImpl.prototype.socket; 810 | /* true once we have received an acknowledgement to a CONNECT packet. */ 811 | ClientImpl.prototype.connected = false; 812 | /* The largest message identifier allowed, may not be larger than 2**16 but 813 | * if set smaller reduces the maximum number of outbound messages allowed. 814 | */ 815 | ClientImpl.prototype.maxMessageIdentifier = 65536; 816 | ClientImpl.prototype.connectOptions; 817 | ClientImpl.prototype.hostIndex; 818 | ClientImpl.prototype.onConnectionLost; 819 | ClientImpl.prototype.onMessageDelivered; 820 | ClientImpl.prototype.onMessageArrived; 821 | ClientImpl.prototype.traceFunction; 822 | ClientImpl.prototype._msg_queue = null; 823 | ClientImpl.prototype._connectTimeout; 824 | /* The sendPinger monitors how long we allow before we send data to prove to the server that we are alive. */ 825 | ClientImpl.prototype.sendPinger = null; 826 | /* The receivePinger monitors how long we allow before we require evidence that the server is alive. */ 827 | ClientImpl.prototype.receivePinger = null; 828 | 829 | ClientImpl.prototype.receiveBuffer = null; 830 | 831 | ClientImpl.prototype._traceBuffer = null; 832 | ClientImpl.prototype._MAX_TRACE_ENTRIES = 100; 833 | 834 | ClientImpl.prototype.connect = function(connectOptions) { 835 | var connectOptionsMasked = this._traceMask(connectOptions, "password"); 836 | this._trace("Client.connect", connectOptionsMasked, this.socket, this.connected); 837 | 838 | if (this.connected) 839 | throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); 840 | if (this.socket) 841 | throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); 842 | 843 | this.connectOptions = connectOptions; 844 | 845 | if (connectOptions.uris) { 846 | this.hostIndex = 0; 847 | this._doConnect(connectOptions.uris[0]); 848 | } else { 849 | this._doConnect(this.uri); 850 | } 851 | 852 | }; 853 | 854 | ClientImpl.prototype.subscribe = function(filter, subscribeOptions) { 855 | this._trace("Client.subscribe", filter, subscribeOptions); 856 | 857 | if (!this.connected) 858 | throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); 859 | 860 | var wireMessage = new WireMessage(MESSAGE_TYPE.SUBSCRIBE); 861 | wireMessage.topics = [filter]; 862 | if (subscribeOptions.qos != undefined) 863 | wireMessage.requestedQos = [subscribeOptions.qos]; 864 | else 865 | wireMessage.requestedQos = [0]; 866 | 867 | if (subscribeOptions.onSuccess) { 868 | wireMessage.onSuccess = function(grantedQos) { 869 | subscribeOptions.onSuccess({invocationContext: subscribeOptions.invocationContext, grantedQos: grantedQos}); 870 | }; 871 | } 872 | 873 | if (subscribeOptions.onFailure) { 874 | wireMessage.onFailure = function(errorCode) { 875 | subscribeOptions.onFailure({invocationContext: subscribeOptions.invocationContext, errorCode: errorCode}); 876 | }; 877 | } 878 | 879 | if (subscribeOptions.timeout) { 880 | wireMessage.timeOut = new Timeout(this, window, subscribeOptions.timeout, subscribeOptions.onFailure 881 | , [{ 882 | invocationContext: subscribeOptions.invocationContext, 883 | errorCode: ERROR.SUBSCRIBE_TIMEOUT.code, 884 | errorMessage: format(ERROR.SUBSCRIBE_TIMEOUT) 885 | }]); 886 | } 887 | 888 | // All subscriptions return a SUBACK. 889 | this._requires_ack(wireMessage); 890 | this._schedule_message(wireMessage); 891 | }; 892 | 893 | /** @ignore */ 894 | ClientImpl.prototype.unsubscribe = function(filter, unsubscribeOptions) { 895 | this._trace("Client.unsubscribe", filter, unsubscribeOptions); 896 | 897 | if (!this.connected) 898 | throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); 899 | 900 | var wireMessage = new WireMessage(MESSAGE_TYPE.UNSUBSCRIBE); 901 | wireMessage.topics = [filter]; 902 | 903 | if (unsubscribeOptions.onSuccess) { 904 | wireMessage.callback = function() { 905 | unsubscribeOptions.onSuccess({invocationContext: unsubscribeOptions.invocationContext}); 906 | }; 907 | } 908 | if (unsubscribeOptions.timeout) { 909 | wireMessage.timeOut = new Timeout(this, window, unsubscribeOptions.timeout, unsubscribeOptions.onFailure 910 | , [{ 911 | invocationContext: unsubscribeOptions.invocationContext, 912 | errorCode: ERROR.UNSUBSCRIBE_TIMEOUT.code, 913 | errorMessage: format(ERROR.UNSUBSCRIBE_TIMEOUT) 914 | }]); 915 | } 916 | 917 | // All unsubscribes return a SUBACK. 918 | this._requires_ack(wireMessage); 919 | this._schedule_message(wireMessage); 920 | }; 921 | 922 | ClientImpl.prototype.send = function(message) { 923 | this._trace("Client.send", message); 924 | 925 | if (!this.connected) 926 | throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); 927 | 928 | wireMessage = new WireMessage(MESSAGE_TYPE.PUBLISH); 929 | wireMessage.payloadMessage = message; 930 | 931 | if (message.qos > 0) 932 | this._requires_ack(wireMessage); 933 | else if (this.onMessageDelivered) 934 | this._notify_msg_sent[wireMessage] = this.onMessageDelivered(wireMessage.payloadMessage); 935 | this._schedule_message(wireMessage); 936 | }; 937 | 938 | ClientImpl.prototype.disconnect = function() { 939 | this._trace("Client.disconnect"); 940 | 941 | if (!this.socket) 942 | throw new Error(format(ERROR.INVALID_STATE, ["not connecting or connected"])); 943 | 944 | wireMessage = new WireMessage(MESSAGE_TYPE.DISCONNECT); 945 | 946 | // Run the disconnected call back as soon as the message has been sent, 947 | // in case of a failure later on in the disconnect processing. 948 | // as a consequence, the _disconected call back may be run several times. 949 | this._notify_msg_sent[wireMessage] = scope(this._disconnected, this); 950 | 951 | this._schedule_message(wireMessage); 952 | }; 953 | 954 | ClientImpl.prototype.getTraceLog = function() { 955 | if (this._traceBuffer !== null) { 956 | this._trace("Client.getTraceLog", new Date()); 957 | this._trace("Client.getTraceLog in flight messages", this._sentMessages.length); 958 | for(var key in this._sentMessages) 959 | this._trace("_sentMessages ", key, this._sentMessages[key]); 960 | for(var key in this._receivedMessages) 961 | this._trace("_receivedMessages ", key, this._receivedMessages[key]); 962 | 963 | return this._traceBuffer; 964 | } 965 | }; 966 | 967 | ClientImpl.prototype.startTrace = function() { 968 | if (this._traceBuffer === null) { 969 | this._traceBuffer = []; 970 | } 971 | this._trace("Client.startTrace", new Date(), version); 972 | }; 973 | 974 | ClientImpl.prototype.stopTrace = function() { 975 | delete this._traceBuffer; 976 | }; 977 | 978 | ClientImpl.prototype._doConnect = function(wsurl) { 979 | // When the socket is open, this client will send the CONNECT WireMessage using the saved parameters. 980 | if (this.connectOptions.useSSL) { 981 | var uriParts = wsurl.split(":"); 982 | uriParts[0] = "wss"; 983 | wsurl = uriParts.join(":"); 984 | } 985 | this.connected = false; 986 | if (this.connectOptions.mqttVersion < 4) { 987 | this.socket = new WebSocket(wsurl, ["mqttv3.1"]); 988 | } else { 989 | this.socket = new WebSocket(wsurl, ["mqtt"]); 990 | } 991 | this.socket.binaryType = 'arraybuffer'; 992 | 993 | this.socket.onopen = scope(this._on_socket_open, this); 994 | this.socket.onmessage = scope(this._on_socket_message, this); 995 | this.socket.onerror = scope(this._on_socket_error, this); 996 | this.socket.onclose = scope(this._on_socket_close, this); 997 | 998 | this.sendPinger = new Pinger(this, window, this.connectOptions.keepAliveInterval); 999 | this.receivePinger = new Pinger(this, window, this.connectOptions.keepAliveInterval); 1000 | 1001 | this._connectTimeout = new Timeout(this, window, this.connectOptions.timeout, this._disconnected, [ERROR.CONNECT_TIMEOUT.code, format(ERROR.CONNECT_TIMEOUT)]); 1002 | }; 1003 | 1004 | 1005 | // Schedule a new message to be sent over the WebSockets 1006 | // connection. CONNECT messages cause WebSocket connection 1007 | // to be started. All other messages are queued internally 1008 | // until this has happened. When WS connection starts, process 1009 | // all outstanding messages. 1010 | ClientImpl.prototype._schedule_message = function(message) { 1011 | this._msg_queue.push(message); 1012 | // Process outstanding messages in the queue if we have an open socket, and have received CONNACK. 1013 | if (this.connected) { 1014 | this._process_queue(); 1015 | } 1016 | }; 1017 | 1018 | ClientImpl.prototype.store = function(prefix, wireMessage) { 1019 | var storedMessage = {type: wireMessage.type, messageIdentifier: wireMessage.messageIdentifier, version: 1}; 1020 | 1021 | switch (wireMessage.type) { 1022 | case MESSAGE_TYPE.PUBLISH: 1023 | if (wireMessage.pubRecReceived) 1024 | storedMessage.pubRecReceived = true; 1025 | 1026 | // Convert the payload to a hex string. 1027 | storedMessage.payloadMessage = {}; 1028 | var hex = ""; 1029 | var messageBytes = wireMessage.payloadMessage.payloadBytes; 1030 | for(var i = 0; i < messageBytes.length; i++) { 1031 | if (messageBytes[i] <= 0xF) 1032 | hex = hex + "0" + messageBytes[i].toString(16); 1033 | else 1034 | hex = hex + messageBytes[i].toString(16); 1035 | } 1036 | storedMessage.payloadMessage.payloadHex = hex; 1037 | 1038 | storedMessage.payloadMessage.qos = wireMessage.payloadMessage.qos; 1039 | storedMessage.payloadMessage.destinationName = wireMessage.payloadMessage.destinationName; 1040 | if (wireMessage.payloadMessage.duplicate) 1041 | storedMessage.payloadMessage.duplicate = true; 1042 | if (wireMessage.payloadMessage.retained) 1043 | storedMessage.payloadMessage.retained = true; 1044 | 1045 | // Add a sequence number to sent messages. 1046 | if (prefix.indexOf("Sent:") == 0) { 1047 | if (wireMessage.sequence === undefined) 1048 | wireMessage.sequence = ++this._sequence; 1049 | storedMessage.sequence = wireMessage.sequence; 1050 | } 1051 | break; 1052 | 1053 | default: 1054 | throw Error(format(ERROR.INVALID_STORED_DATA, [key, storedMessage])); 1055 | } 1056 | localStorage.setItem(prefix + this._localKey + wireMessage.messageIdentifier, JSON.stringify(storedMessage)); 1057 | }; 1058 | 1059 | ClientImpl.prototype.restore = function(key) { 1060 | var value = localStorage.getItem(key); 1061 | var storedMessage = JSON.parse(value); 1062 | 1063 | var wireMessage = new WireMessage(storedMessage.type, storedMessage); 1064 | 1065 | switch (storedMessage.type) { 1066 | case MESSAGE_TYPE.PUBLISH: 1067 | // Replace the payload message with a Message object. 1068 | var hex = storedMessage.payloadMessage.payloadHex; 1069 | var buffer = new ArrayBuffer((hex.length) / 2); 1070 | var byteStream = new Uint8Array(buffer); 1071 | var i = 0; 1072 | while (hex.length >= 2) { 1073 | var x = parseInt(hex.substring(0, 2), 16); 1074 | hex = hex.substring(2, hex.length); 1075 | byteStream[i++] = x; 1076 | } 1077 | var payloadMessage = new Paho.MQTT.Message(byteStream); 1078 | 1079 | payloadMessage.qos = storedMessage.payloadMessage.qos; 1080 | payloadMessage.destinationName = storedMessage.payloadMessage.destinationName; 1081 | if (storedMessage.payloadMessage.duplicate) 1082 | payloadMessage.duplicate = true; 1083 | if (storedMessage.payloadMessage.retained) 1084 | payloadMessage.retained = true; 1085 | wireMessage.payloadMessage = payloadMessage; 1086 | 1087 | break; 1088 | 1089 | default: 1090 | throw Error(format(ERROR.INVALID_STORED_DATA, [key, value])); 1091 | } 1092 | 1093 | if (key.indexOf("Sent:" + this._localKey) == 0) { 1094 | wireMessage.payloadMessage.duplicate = true; 1095 | this._sentMessages[wireMessage.messageIdentifier] = wireMessage; 1096 | } else if (key.indexOf("Received:" + this._localKey) == 0) { 1097 | this._receivedMessages[wireMessage.messageIdentifier] = wireMessage; 1098 | } 1099 | }; 1100 | 1101 | ClientImpl.prototype._process_queue = function() { 1102 | var message = null; 1103 | // Process messages in order they were added 1104 | var fifo = this._msg_queue.reverse(); 1105 | 1106 | // Send all queued messages down socket connection 1107 | while ((message = fifo.pop())) { 1108 | this._socket_send(message); 1109 | // Notify listeners that message was successfully sent 1110 | if (this._notify_msg_sent[message]) { 1111 | this._notify_msg_sent[message](); 1112 | delete this._notify_msg_sent[message]; 1113 | } 1114 | } 1115 | }; 1116 | 1117 | /** 1118 | * Expect an ACK response for this message. Add message to the set of in progress 1119 | * messages and set an unused identifier in this message. 1120 | * @ignore 1121 | */ 1122 | ClientImpl.prototype._requires_ack = function(wireMessage) { 1123 | var messageCount = Object.keys(this._sentMessages).length; 1124 | if (messageCount > this.maxMessageIdentifier) 1125 | throw Error("Too many messages:" + messageCount); 1126 | 1127 | while (this._sentMessages[this._message_identifier] !== undefined) { 1128 | this._message_identifier++; 1129 | } 1130 | wireMessage.messageIdentifier = this._message_identifier; 1131 | this._sentMessages[wireMessage.messageIdentifier] = wireMessage; 1132 | if (wireMessage.type === MESSAGE_TYPE.PUBLISH) { 1133 | this.store("Sent:", wireMessage); 1134 | } 1135 | if (this._message_identifier === this.maxMessageIdentifier) { 1136 | this._message_identifier = 1; 1137 | } 1138 | }; 1139 | 1140 | /** 1141 | * Called when the underlying websocket has been opened. 1142 | * @ignore 1143 | */ 1144 | ClientImpl.prototype._on_socket_open = function() { 1145 | // Create the CONNECT message object. 1146 | var wireMessage = new WireMessage(MESSAGE_TYPE.CONNECT, this.connectOptions); 1147 | wireMessage.clientId = this.clientId; 1148 | this._socket_send(wireMessage); 1149 | }; 1150 | 1151 | /** 1152 | * Called when the underlying websocket has received a complete packet. 1153 | * @ignore 1154 | */ 1155 | ClientImpl.prototype._on_socket_message = function(event) { 1156 | this._trace("Client._on_socket_message", event.data); 1157 | var messages = this._deframeMessages(event.data); 1158 | for(var i = 0; i < messages.length; i += 1) { 1159 | this._handleMessage(messages[i]); 1160 | } 1161 | } 1162 | 1163 | ClientImpl.prototype._deframeMessages = function(data) { 1164 | var byteArray = new Uint8Array(data); 1165 | if (this.receiveBuffer) { 1166 | var newData = new Uint8Array(this.receiveBuffer.length + byteArray.length); 1167 | newData.set(this.receiveBuffer); 1168 | newData.set(byteArray, this.receiveBuffer.length); 1169 | byteArray = newData; 1170 | delete this.receiveBuffer; 1171 | } 1172 | try { 1173 | var offset = 0; 1174 | var messages = []; 1175 | while (offset < byteArray.length) { 1176 | var result = decodeMessage(byteArray, offset); 1177 | var wireMessage = result[0]; 1178 | offset = result[1]; 1179 | if (wireMessage !== null) { 1180 | messages.push(wireMessage); 1181 | } else { 1182 | break; 1183 | } 1184 | } 1185 | if (offset < byteArray.length) { 1186 | this.receiveBuffer = byteArray.subarray(offset); 1187 | } 1188 | } catch (error) { 1189 | this._disconnected(ERROR.INTERNAL_ERROR.code, format(ERROR.INTERNAL_ERROR, [error.message, error.stack.toString()])); 1190 | return; 1191 | } 1192 | return messages; 1193 | } 1194 | 1195 | ClientImpl.prototype._handleMessage = function(wireMessage) { 1196 | 1197 | this._trace("Client._handleMessage", wireMessage); 1198 | 1199 | try { 1200 | switch (wireMessage.type) { 1201 | case MESSAGE_TYPE.CONNACK: 1202 | this._connectTimeout.cancel(); 1203 | 1204 | // If we have started using clean session then clear up the local state. 1205 | if (this.connectOptions.cleanSession) { 1206 | for(var key in this._sentMessages) { 1207 | var sentMessage = this._sentMessages[key]; 1208 | localStorage.removeItem("Sent:" + this._localKey + sentMessage.messageIdentifier); 1209 | } 1210 | this._sentMessages = {}; 1211 | 1212 | for(var key in this._receivedMessages) { 1213 | var receivedMessage = this._receivedMessages[key]; 1214 | localStorage.removeItem("Received:" + this._localKey + receivedMessage.messageIdentifier); 1215 | } 1216 | this._receivedMessages = {}; 1217 | } 1218 | // Client connected and ready for business. 1219 | if (wireMessage.returnCode === 0) { 1220 | this.connected = true; 1221 | // Jump to the end of the list of uris and stop looking for a good host. 1222 | if (this.connectOptions.uris) 1223 | this.hostIndex = this.connectOptions.uris.length; 1224 | } else { 1225 | this._disconnected(ERROR.CONNACK_RETURNCODE.code, format(ERROR.CONNACK_RETURNCODE, [wireMessage.returnCode, CONNACK_RC[wireMessage.returnCode]])); 1226 | break; 1227 | } 1228 | 1229 | // Resend messages. 1230 | var sequencedMessages = new Array(); 1231 | for(var msgId in this._sentMessages) { 1232 | if (this._sentMessages.hasOwnProperty(msgId)) 1233 | sequencedMessages.push(this._sentMessages[msgId]); 1234 | } 1235 | 1236 | // Sort sentMessages into the original sent order. 1237 | var sequencedMessages = sequencedMessages.sort(function(a, b) { 1238 | return a.sequence - b.sequence; 1239 | }); 1240 | for(var i = 0, len = sequencedMessages.length; i < len; i++) { 1241 | var sentMessage = sequencedMessages[i]; 1242 | if (sentMessage.type == MESSAGE_TYPE.PUBLISH && sentMessage.pubRecReceived) { 1243 | var pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier: sentMessage.messageIdentifier}); 1244 | this._schedule_message(pubRelMessage); 1245 | } else { 1246 | this._schedule_message(sentMessage); 1247 | } 1248 | ; 1249 | } 1250 | 1251 | // Execute the connectOptions.onSuccess callback if there is one. 1252 | if (this.connectOptions.onSuccess) { 1253 | this.connectOptions.onSuccess({invocationContext: this.connectOptions.invocationContext}); 1254 | } 1255 | 1256 | // Process all queued messages now that the connection is established. 1257 | this._process_queue(); 1258 | break; 1259 | 1260 | case MESSAGE_TYPE.PUBLISH: 1261 | this._receivePublish(wireMessage); 1262 | break; 1263 | 1264 | case MESSAGE_TYPE.PUBACK: 1265 | var sentMessage = this._sentMessages[wireMessage.messageIdentifier]; 1266 | // If this is a re flow of a PUBACK after we have restarted receivedMessage will not exist. 1267 | if (sentMessage) { 1268 | delete this._sentMessages[wireMessage.messageIdentifier]; 1269 | localStorage.removeItem("Sent:" + this._localKey + wireMessage.messageIdentifier); 1270 | if (this.onMessageDelivered) 1271 | this.onMessageDelivered(sentMessage.payloadMessage); 1272 | } 1273 | break; 1274 | 1275 | case MESSAGE_TYPE.PUBREC: 1276 | var sentMessage = this._sentMessages[wireMessage.messageIdentifier]; 1277 | // If this is a re flow of a PUBREC after we have restarted receivedMessage will not exist. 1278 | if (sentMessage) { 1279 | sentMessage.pubRecReceived = true; 1280 | var pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier: wireMessage.messageIdentifier}); 1281 | this.store("Sent:", sentMessage); 1282 | this._schedule_message(pubRelMessage); 1283 | } 1284 | break; 1285 | 1286 | case MESSAGE_TYPE.PUBREL: 1287 | var receivedMessage = this._receivedMessages[wireMessage.messageIdentifier]; 1288 | localStorage.removeItem("Received:" + this._localKey + wireMessage.messageIdentifier); 1289 | // If this is a re flow of a PUBREL after we have restarted receivedMessage will not exist. 1290 | if (receivedMessage) { 1291 | this._receiveMessage(receivedMessage); 1292 | delete this._receivedMessages[wireMessage.messageIdentifier]; 1293 | } 1294 | // Always flow PubComp, we may have previously flowed PubComp but the server lost it and restarted. 1295 | var pubCompMessage = new WireMessage(MESSAGE_TYPE.PUBCOMP, {messageIdentifier: wireMessage.messageIdentifier}); 1296 | this._schedule_message(pubCompMessage); 1297 | break; 1298 | 1299 | case MESSAGE_TYPE.PUBCOMP: 1300 | var sentMessage = this._sentMessages[wireMessage.messageIdentifier]; 1301 | delete this._sentMessages[wireMessage.messageIdentifier]; 1302 | localStorage.removeItem("Sent:" + this._localKey + wireMessage.messageIdentifier); 1303 | if (this.onMessageDelivered) 1304 | this.onMessageDelivered(sentMessage.payloadMessage); 1305 | break; 1306 | 1307 | case MESSAGE_TYPE.SUBACK: 1308 | var sentMessage = this._sentMessages[wireMessage.messageIdentifier]; 1309 | if (sentMessage) { 1310 | if (sentMessage.timeOut) 1311 | sentMessage.timeOut.cancel(); 1312 | // This will need to be fixed when we add multiple topic support 1313 | if (wireMessage.returnCode[0] === 0x80) { 1314 | if (sentMessage.onFailure) { 1315 | sentMessage.onFailure(wireMessage.returnCode); 1316 | } 1317 | } else if (sentMessage.onSuccess) { 1318 | sentMessage.onSuccess(wireMessage.returnCode); 1319 | } 1320 | delete this._sentMessages[wireMessage.messageIdentifier]; 1321 | } 1322 | break; 1323 | 1324 | case MESSAGE_TYPE.UNSUBACK: 1325 | var sentMessage = this._sentMessages[wireMessage.messageIdentifier]; 1326 | if (sentMessage) { 1327 | if (sentMessage.timeOut) 1328 | sentMessage.timeOut.cancel(); 1329 | if (sentMessage.callback) { 1330 | sentMessage.callback(); 1331 | } 1332 | delete this._sentMessages[wireMessage.messageIdentifier]; 1333 | } 1334 | 1335 | break; 1336 | 1337 | case MESSAGE_TYPE.PINGRESP: 1338 | /* The sendPinger or receivePinger may have sent a ping, the receivePinger has already been reset. */ 1339 | this.sendPinger.reset(); 1340 | break; 1341 | 1342 | case MESSAGE_TYPE.DISCONNECT: 1343 | // Clients do not expect to receive disconnect packets. 1344 | this._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code, format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type])); 1345 | break; 1346 | 1347 | default: 1348 | this._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code, format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type])); 1349 | } 1350 | ; 1351 | } catch (error) { 1352 | this._disconnected(ERROR.INTERNAL_ERROR.code, format(ERROR.INTERNAL_ERROR, [error.message, error.stack.toString()])); 1353 | return; 1354 | } 1355 | }; 1356 | 1357 | /** @ignore */ 1358 | ClientImpl.prototype._on_socket_error = function(error) { 1359 | this._disconnected(ERROR.SOCKET_ERROR.code, format(ERROR.SOCKET_ERROR, [error.data])); 1360 | }; 1361 | 1362 | /** @ignore */ 1363 | ClientImpl.prototype._on_socket_close = function() { 1364 | this._disconnected(ERROR.SOCKET_CLOSE.code, format(ERROR.SOCKET_CLOSE)); 1365 | }; 1366 | 1367 | /** @ignore */ 1368 | ClientImpl.prototype._socket_send = function(wireMessage) { 1369 | 1370 | if (wireMessage.type == 1) { 1371 | var wireMessageMasked = this._traceMask(wireMessage, "password"); 1372 | this._trace("Client._socket_send", wireMessageMasked); 1373 | } 1374 | else this._trace("Client._socket_send", wireMessage); 1375 | try { 1376 | this.socket.send(wireMessage.encode()); 1377 | /* We have proved to the server we are alive. */ 1378 | this.sendPinger.reset(); 1379 | } catch (e) { 1380 | this._trace("Client._socket_send", format(ERROR.SOCKET_ERROR, [e.message])); 1381 | this._on_socket_error({data: e.message}); 1382 | } 1383 | }; 1384 | 1385 | /** @ignore */ 1386 | ClientImpl.prototype._receivePublish = function(wireMessage) { 1387 | switch (wireMessage.payloadMessage.qos) { 1388 | case "undefined": 1389 | case 0: 1390 | this._receiveMessage(wireMessage); 1391 | break; 1392 | 1393 | case 1: 1394 | var pubAckMessage = new WireMessage(MESSAGE_TYPE.PUBACK, {messageIdentifier: wireMessage.messageIdentifier}); 1395 | this._schedule_message(pubAckMessage); 1396 | this._receiveMessage(wireMessage); 1397 | break; 1398 | 1399 | case 2: 1400 | this._receivedMessages[wireMessage.messageIdentifier] = wireMessage; 1401 | this.store("Received:", wireMessage); 1402 | var pubRecMessage = new WireMessage(MESSAGE_TYPE.PUBREC, {messageIdentifier: wireMessage.messageIdentifier}); 1403 | this._schedule_message(pubRecMessage); 1404 | 1405 | break; 1406 | 1407 | default: 1408 | throw Error("Invaild qos=" + wireMmessage.payloadMessage.qos); 1409 | } 1410 | ; 1411 | }; 1412 | 1413 | /** @ignore */ 1414 | ClientImpl.prototype._receiveMessage = function(wireMessage) { 1415 | if (this.onMessageArrived) { 1416 | this.onMessageArrived(wireMessage.payloadMessage); 1417 | } 1418 | }; 1419 | 1420 | /** 1421 | * Client has disconnected either at its own request or because the server 1422 | * or network disconnected it. Remove all non-durable state. 1423 | * @param {errorCode} [number] the error number. 1424 | * @param {errorText} [string] the error text. 1425 | * @ignore 1426 | */ 1427 | ClientImpl.prototype._disconnected = function(errorCode, errorText) { 1428 | this._trace("Client._disconnected", errorCode, errorText); 1429 | 1430 | this.sendPinger.cancel(); 1431 | this.receivePinger.cancel(); 1432 | if (this._connectTimeout) 1433 | this._connectTimeout.cancel(); 1434 | // Clear message buffers. 1435 | this._msg_queue = []; 1436 | this._notify_msg_sent = {}; 1437 | 1438 | if (this.socket) { 1439 | // Cancel all socket callbacks so that they cannot be driven again by this socket. 1440 | this.socket.onopen = null; 1441 | this.socket.onmessage = null; 1442 | this.socket.onerror = null; 1443 | this.socket.onclose = null; 1444 | if (this.socket.readyState === 1) 1445 | try { 1446 | this.socket.close(); 1447 | } catch (e) { 1448 | this._trace("Client._disconnected", format(ERROR.SOCKET_ERROR, [e.message])); 1449 | } 1450 | delete this.socket; 1451 | } 1452 | 1453 | if (this.connectOptions.uris && this.hostIndex < this.connectOptions.uris.length - 1) { 1454 | // Try the next host. 1455 | this.hostIndex++; 1456 | this._doConnect(this.connectOptions.uris[this.hostIndex]); 1457 | 1458 | } else { 1459 | 1460 | if (errorCode === undefined) { 1461 | errorCode = ERROR.OK.code; 1462 | errorText = format(ERROR.OK); 1463 | } 1464 | 1465 | // Run any application callbacks last as they may attempt to reconnect and hence create a new socket. 1466 | if (this.connected) { 1467 | this.connected = false; 1468 | // Execute the connectionLostCallback if there is one, and we were connected. 1469 | if (this.onConnectionLost) 1470 | this.onConnectionLost({errorCode: errorCode, errorMessage: errorText}); 1471 | } else { 1472 | // Otherwise we never had a connection, so indicate that the connect has failed. 1473 | if (this.connectOptions.mqttVersion === 4 && this.connectOptions.mqttVersionExplicit === false) { 1474 | this._trace("Failed to connect V4, dropping back to V3") 1475 | this.connectOptions.mqttVersion = 3; 1476 | if (this.connectOptions.uris) { 1477 | this.hostIndex = 0; 1478 | this._doConnect(this.connectOptions.uris[0]); 1479 | } else { 1480 | this._doConnect(this.uri); 1481 | } 1482 | } else if (this.connectOptions.onFailure) { 1483 | this.connectOptions.onFailure({ 1484 | invocationContext: this.connectOptions.invocationContext, 1485 | errorCode: errorCode, 1486 | errorMessage: errorText 1487 | }); 1488 | } 1489 | } 1490 | } 1491 | }; 1492 | 1493 | /** @ignore */ 1494 | ClientImpl.prototype._trace = function() { 1495 | // Pass trace message back to client's callback function 1496 | if (this.traceFunction) { 1497 | for(var i in arguments) { 1498 | if (typeof arguments[i] !== "undefined") 1499 | arguments[i] = JSON.stringify(arguments[i]); 1500 | } 1501 | var record = Array.prototype.slice.call(arguments).join(""); 1502 | this.traceFunction({severity: "Debug", message: record}); 1503 | } 1504 | 1505 | //buffer style trace 1506 | if (this._traceBuffer !== null) { 1507 | for(var i = 0, max = arguments.length; i < max; i++) { 1508 | if (this._traceBuffer.length == this._MAX_TRACE_ENTRIES) { 1509 | this._traceBuffer.shift(); 1510 | } 1511 | if (i === 0) this._traceBuffer.push(arguments[i]); 1512 | else if (typeof arguments[i] === "undefined") this._traceBuffer.push(arguments[i]); 1513 | else this._traceBuffer.push(" " + JSON.stringify(arguments[i])); 1514 | } 1515 | ; 1516 | } 1517 | ; 1518 | }; 1519 | 1520 | /** @ignore */ 1521 | ClientImpl.prototype._traceMask = function(traceObject, masked) { 1522 | var traceObjectMasked = {}; 1523 | for(var attr in traceObject) { 1524 | if (traceObject.hasOwnProperty(attr)) { 1525 | if (attr == masked) 1526 | traceObjectMasked[attr] = "******"; 1527 | else 1528 | traceObjectMasked[attr] = traceObject[attr]; 1529 | } 1530 | } 1531 | return traceObjectMasked; 1532 | }; 1533 | 1534 | // ------------------------------------------------------------------------ 1535 | // Public Programming interface. 1536 | // ------------------------------------------------------------------------ 1537 | 1538 | /** 1539 | * The JavaScript application communicates to the server using a {@link Paho.MQTT.Client} object. 1540 | *

1541 | * Most applications will create just one Client object and then call its connect() method, 1542 | * however applications can create more than one Client object if they wish. 1543 | * In this case the combination of host, port and clientId attributes must be different for each Client object. 1544 | *

1545 | * The send, subscribe and unsubscribe methods are implemented as asynchronous JavaScript methods 1546 | * (even though the underlying protocol exchange might be synchronous in nature). 1547 | * This means they signal their completion by calling back to the application, 1548 | * via Success or Failure callback functions provided by the application on the method in question. 1549 | * Such callbacks are called at most once per method invocation and do not persist beyond the lifetime 1550 | * of the script that made the invocation. 1551 | *

1552 | * In contrast there are some callback functions, most notably onMessageArrived, 1553 | * that are defined on the {@link Paho.MQTT.Client} object. 1554 | * These may get called multiple times, and aren't directly related to specific method invocations made by the client. 1555 | * 1556 | * @name Paho.MQTT.Client 1557 | * 1558 | * @constructor 1559 | * 1560 | * @param {string} host - the address of the messaging server, as a fully qualified WebSocket URI, as a DNS name or dotted decimal IP address. 1561 | * @param {number} port - the port number to connect to - only required if host is not a URI 1562 | * @param {string} path - the path on the host to connect to - only used if host is not a URI. Default: '/mqtt'. 1563 | * @param {string} clientId - the Messaging client identifier, between 1 and 23 characters in length. 1564 | * 1565 | * @property {string} host - read only the server's DNS hostname or dotted decimal IP address. 1566 | * @property {number} port - read only the server's port. 1567 | * @property {string} path - read only the server's path. 1568 | * @property {string} clientId - read only used when connecting to the server. 1569 | * @property {function} onConnectionLost - called when a connection has been lost. 1570 | * after a connect() method has succeeded. 1571 | * Establish the call back used when a connection has been lost. The connection may be 1572 | * lost because the client initiates a disconnect or because the server or network 1573 | * cause the client to be disconnected. The disconnect call back may be called without 1574 | * the connectionComplete call back being invoked if, for example the client fails to 1575 | * connect. 1576 | * A single response object parameter is passed to the onConnectionLost callback containing the following fields: 1577 | *

    1578 | *
  1. errorCode 1579 | *
  2. errorMessage 1580 | *
1581 | * @property {function} onMessageDelivered called when a message has been delivered. 1582 | * All processing that this Client will ever do has been completed. So, for example, 1583 | * in the case of a Qos=2 message sent by this client, the PubComp flow has been received from the server 1584 | * and the message has been removed from persistent storage before this callback is invoked. 1585 | * Parameters passed to the onMessageDelivered callback are: 1586 | *
    1587 | *
  1. {@link Paho.MQTT.Message} that was delivered. 1588 | *
1589 | * @property {function} onMessageArrived called when a message has arrived in this Paho.MQTT.client. 1590 | * Parameters passed to the onMessageArrived callback are: 1591 | *
    1592 | *
  1. {@link Paho.MQTT.Message} that has arrived. 1593 | *
1594 | */ 1595 | var Client = function(host, port, path, clientId) { 1596 | 1597 | var uri; 1598 | 1599 | if (typeof host !== "string") 1600 | throw new Error(format(ERROR.INVALID_TYPE, [typeof host, "host"])); 1601 | 1602 | if (arguments.length == 2) { 1603 | // host: must be full ws:// uri 1604 | // port: clientId 1605 | clientId = port; 1606 | uri = host; 1607 | var match = uri.match(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/); 1608 | if (match) { 1609 | host = match[4] || match[2]; 1610 | port = parseInt(match[7]); 1611 | path = match[8]; 1612 | } else { 1613 | throw new Error(format(ERROR.INVALID_ARGUMENT, [host, "host"])); 1614 | } 1615 | } else { 1616 | if (arguments.length == 3) { 1617 | clientId = path; 1618 | path = "/mqtt"; 1619 | } 1620 | if (typeof port !== "number" || port < 0) 1621 | throw new Error(format(ERROR.INVALID_TYPE, [typeof port, "port"])); 1622 | if (typeof path !== "string") 1623 | throw new Error(format(ERROR.INVALID_TYPE, [typeof path, "path"])); 1624 | 1625 | var ipv6AddSBracket = (host.indexOf(":") != -1 && host.slice(0, 1) != "[" && host.slice(-1) != "]"); 1626 | uri = "ws://" + (ipv6AddSBracket ? "[" + host + "]" : host) + ":" + port + path; 1627 | } 1628 | 1629 | var clientIdLength = 0; 1630 | for(var i = 0; i < clientId.length; i++) { 1631 | var charCode = clientId.charCodeAt(i); 1632 | if (0xD800 <= charCode && charCode <= 0xDBFF) { 1633 | i++; // Surrogate pair. 1634 | } 1635 | clientIdLength++; 1636 | } 1637 | if (typeof clientId !== "string" || clientIdLength > 65535) 1638 | throw new Error(format(ERROR.INVALID_ARGUMENT, [clientId, "clientId"])); 1639 | 1640 | var client = new ClientImpl(uri, host, port, path, clientId); 1641 | this._getHost = function() { 1642 | return host; 1643 | }; 1644 | this._setHost = function() { 1645 | throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); 1646 | }; 1647 | 1648 | this._getPort = function() { 1649 | return port; 1650 | }; 1651 | this._setPort = function() { 1652 | throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); 1653 | }; 1654 | 1655 | this._getPath = function() { 1656 | return path; 1657 | }; 1658 | this._setPath = function() { 1659 | throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); 1660 | }; 1661 | 1662 | this._getURI = function() { 1663 | return uri; 1664 | }; 1665 | this._setURI = function() { 1666 | throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); 1667 | }; 1668 | 1669 | this._getClientId = function() { 1670 | return client.clientId; 1671 | }; 1672 | this._setClientId = function() { 1673 | throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); 1674 | }; 1675 | 1676 | this._getOnConnectionLost = function() { 1677 | return client.onConnectionLost; 1678 | }; 1679 | this._setOnConnectionLost = function(newOnConnectionLost) { 1680 | if (typeof newOnConnectionLost === "function") 1681 | client.onConnectionLost = newOnConnectionLost; 1682 | else 1683 | throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnectionLost, "onConnectionLost"])); 1684 | }; 1685 | 1686 | this._getOnMessageDelivered = function() { 1687 | return client.onMessageDelivered; 1688 | }; 1689 | this._setOnMessageDelivered = function(newOnMessageDelivered) { 1690 | if (typeof newOnMessageDelivered === "function") 1691 | client.onMessageDelivered = newOnMessageDelivered; 1692 | else 1693 | throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageDelivered, "onMessageDelivered"])); 1694 | }; 1695 | 1696 | this._getOnMessageArrived = function() { 1697 | return client.onMessageArrived; 1698 | }; 1699 | this._setOnMessageArrived = function(newOnMessageArrived) { 1700 | if (typeof newOnMessageArrived === "function") 1701 | client.onMessageArrived = newOnMessageArrived; 1702 | else 1703 | throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageArrived, "onMessageArrived"])); 1704 | }; 1705 | 1706 | this._getTrace = function() { 1707 | return client.traceFunction; 1708 | }; 1709 | this._setTrace = function(trace) { 1710 | if (typeof trace === "function") { 1711 | client.traceFunction = trace; 1712 | } else { 1713 | throw new Error(format(ERROR.INVALID_TYPE, [typeof trace, "onTrace"])); 1714 | } 1715 | }; 1716 | 1717 | /** 1718 | * Connect this Messaging client to its server. 1719 | * 1720 | * @name Paho.MQTT.Client#connect 1721 | * @function 1722 | * @param {Object} connectOptions - attributes used with the connection. 1723 | * @param {number} connectOptions.timeout - If the connect has not succeeded within this 1724 | * number of seconds, it is deemed to have failed. 1725 | * The default is 30 seconds. 1726 | * @param {string} connectOptions.userName - Authentication username for this connection. 1727 | * @param {string} connectOptions.password - Authentication password for this connection. 1728 | * @param {Paho.MQTT.Message} connectOptions.willMessage - sent by the server when the client 1729 | * disconnects abnormally. 1730 | * @param {Number} connectOptions.keepAliveInterval - the server disconnects this client if 1731 | * there is no activity for this number of seconds. 1732 | * The default value of 60 seconds is assumed if not set. 1733 | * @param {boolean} connectOptions.cleanSession - if true(default) the client and server 1734 | * persistent state is deleted on successful connect. 1735 | * @param {boolean} connectOptions.useSSL - if present and true, use an SSL Websocket connection. 1736 | * @param {object} connectOptions.invocationContext - passed to the onSuccess callback or onFailure callback. 1737 | * @param {function} connectOptions.onSuccess - called when the connect acknowledgement 1738 | * has been received from the server. 1739 | * A single response object parameter is passed to the onSuccess callback containing the following fields: 1740 | *
    1741 | *
  1. invocationContext as passed in to the onSuccess method in the connectOptions. 1742 | *
1743 | * @config {function} [onFailure] called when the connect request has failed or timed out. 1744 | * A single response object parameter is passed to the onFailure callback containing the following fields: 1745 | *
    1746 | *
  1. invocationContext as passed in to the onFailure method in the connectOptions. 1747 | *
  2. errorCode a number indicating the nature of the error. 1748 | *
  3. errorMessage text describing the error. 1749 | *
1750 | * @config {Array} [hosts] If present this contains either a set of hostnames or fully qualified 1751 | * WebSocket URIs (ws://example.com:1883/mqtt), that are tried in order in place 1752 | * of the host and port paramater on the construtor. The hosts are tried one at at time in order until 1753 | * one of then succeeds. 1754 | * @config {Array} [ports] If present the set of ports matching the hosts. If hosts contains URIs, this property 1755 | * is not used. 1756 | * @throws {InvalidState} if the client is not in disconnected state. The client must have received connectionLost 1757 | * or disconnected before calling connect for a second or subsequent time. 1758 | */ 1759 | this.connect = function(connectOptions) { 1760 | connectOptions = connectOptions || {}; 1761 | validate(connectOptions, { 1762 | timeout: "number", 1763 | userName: "string", 1764 | password: "string", 1765 | willMessage: "object", 1766 | keepAliveInterval: "number", 1767 | cleanSession: "boolean", 1768 | useSSL: "boolean", 1769 | invocationContext: "object", 1770 | onSuccess: "function", 1771 | onFailure: "function", 1772 | hosts: "object", 1773 | ports: "object", 1774 | mqttVersion:"number", 1775 | mqttVersionExplicit:"boolean", 1776 | uris: "object"}); 1777 | 1778 | // If no keep alive interval is set, assume 60 seconds. 1779 | if (connectOptions.keepAliveInterval === undefined) 1780 | connectOptions.keepAliveInterval = 60; 1781 | 1782 | if (connectOptions.mqttVersion > 4 || connectOptions.mqttVersion < 3) { 1783 | throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.mqttVersion, "connectOptions.mqttVersion"])); 1784 | } 1785 | 1786 | if (connectOptions.mqttVersion === undefined) { 1787 | connectOptions.mqttVersionExplicit = false; 1788 | connectOptions.mqttVersion = 4; 1789 | } else { 1790 | connectOptions.mqttVersionExplicit = true; 1791 | } 1792 | 1793 | //Check that if password is set, so is username 1794 | if (connectOptions.password !== undefined && connectOptions.userName === undefined) 1795 | throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.password, "connectOptions.password"])) 1796 | 1797 | if (connectOptions.willMessage) { 1798 | if (!(connectOptions.willMessage instanceof Message)) 1799 | throw new Error(format(ERROR.INVALID_TYPE, [connectOptions.willMessage, "connectOptions.willMessage"])); 1800 | // The will message must have a payload that can be represented as a string. 1801 | // Cause the willMessage to throw an exception if this is not the case. 1802 | connectOptions.willMessage.stringPayload; 1803 | 1804 | if (typeof connectOptions.willMessage.destinationName === "undefined") 1805 | throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.willMessage.destinationName, "connectOptions.willMessage.destinationName"])); 1806 | } 1807 | if (typeof connectOptions.cleanSession === "undefined") 1808 | connectOptions.cleanSession = true; 1809 | if (connectOptions.hosts) { 1810 | 1811 | if (!(connectOptions.hosts instanceof Array)) 1812 | throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"])); 1813 | if (connectOptions.hosts.length < 1) 1814 | throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"])); 1815 | 1816 | var usingURIs = false; 1817 | for(var i = 0; i < connectOptions.hosts.length; i++) { 1818 | if (typeof connectOptions.hosts[i] !== "string") 1819 | throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.hosts[i], "connectOptions.hosts[" + i + "]"])); 1820 | if (/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/.test(connectOptions.hosts[i])) { 1821 | if (i == 0) { 1822 | usingURIs = true; 1823 | } else if (!usingURIs) { 1824 | throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], "connectOptions.hosts[" + i + "]"])); 1825 | } 1826 | } else if (usingURIs) { 1827 | throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], "connectOptions.hosts[" + i + "]"])); 1828 | } 1829 | } 1830 | 1831 | if (!usingURIs) { 1832 | if (!connectOptions.ports) 1833 | throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"])); 1834 | if (!(connectOptions.ports instanceof Array)) 1835 | throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"])); 1836 | if (connectOptions.hosts.length != connectOptions.ports.length) 1837 | throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"])); 1838 | 1839 | connectOptions.uris = []; 1840 | 1841 | for(var i = 0; i < connectOptions.hosts.length; i++) { 1842 | if (typeof connectOptions.ports[i] !== "number" || connectOptions.ports[i] < 0) 1843 | throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.ports[i], "connectOptions.ports[" + i + "]"])); 1844 | var host = connectOptions.hosts[i]; 1845 | var port = connectOptions.ports[i]; 1846 | 1847 | var ipv6 = (host.indexOf(":") != -1); 1848 | uri = "ws://" + (ipv6 ? "[" + host + "]" : host) + ":" + port + path; 1849 | connectOptions.uris.push(uri); 1850 | } 1851 | } else { 1852 | connectOptions.uris = connectOptions.hosts; 1853 | } 1854 | } 1855 | 1856 | client.connect(connectOptions); 1857 | }; 1858 | 1859 | /** 1860 | * Subscribe for messages, request receipt of a copy of messages sent to the destinations described by the filter. 1861 | * 1862 | * @name Paho.MQTT.Client#subscribe 1863 | * @function 1864 | * @param {string} filter describing the destinations to receive messages from. 1865 | *
1866 | * @param {object} subscribeOptions - used to control the subscription 1867 | * 1868 | * @param {number} subscribeOptions.qos - the maiximum qos of any publications sent 1869 | * as a result of making this subscription. 1870 | * @param {object} subscribeOptions.invocationContext - passed to the onSuccess callback 1871 | * or onFailure callback. 1872 | * @param {function} subscribeOptions.onSuccess - called when the subscribe acknowledgement 1873 | * has been received from the server. 1874 | * A single response object parameter is passed to the onSuccess callback containing the following fields: 1875 | *
    1876 | *
  1. invocationContext if set in the subscribeOptions. 1877 | *
1878 | * @param {function} subscribeOptions.onFailure - called when the subscribe request has failed or timed out. 1879 | * A single response object parameter is passed to the onFailure callback containing the following fields: 1880 | *
    1881 | *
  1. invocationContext - if set in the subscribeOptions. 1882 | *
  2. errorCode - a number indicating the nature of the error. 1883 | *
  3. errorMessage - text describing the error. 1884 | *
1885 | * @param {number} subscribeOptions.timeout - which, if present, determines the number of 1886 | * seconds after which the onFailure calback is called. 1887 | * The presence of a timeout does not prevent the onSuccess 1888 | * callback from being called when the subscribe completes. 1889 | * @throws {InvalidState} if the client is not in connected state. 1890 | */ 1891 | this.subscribe = function(filter, subscribeOptions) { 1892 | if (typeof filter !== "string") 1893 | throw new Error("Invalid argument:" + filter); 1894 | subscribeOptions = subscribeOptions || {}; 1895 | validate(subscribeOptions, { 1896 | qos: "number", 1897 | invocationContext: "object", 1898 | onSuccess: "function", 1899 | onFailure: "function", 1900 | timeout: "number" 1901 | }); 1902 | if (subscribeOptions.timeout && !subscribeOptions.onFailure) 1903 | throw new Error("subscribeOptions.timeout specified with no onFailure callback."); 1904 | if (typeof subscribeOptions.qos !== "undefined" 1905 | && !(subscribeOptions.qos === 0 || subscribeOptions.qos === 1 || subscribeOptions.qos === 2 )) 1906 | throw new Error(format(ERROR.INVALID_ARGUMENT, [subscribeOptions.qos, "subscribeOptions.qos"])); 1907 | client.subscribe(filter, subscribeOptions); 1908 | }; 1909 | 1910 | /** 1911 | * Unsubscribe for messages, stop receiving messages sent to destinations described by the filter. 1912 | * 1913 | * @name Paho.MQTT.Client#unsubscribe 1914 | * @function 1915 | * @param {string} filter - describing the destinations to receive messages from. 1916 | * @param {object} unsubscribeOptions - used to control the subscription 1917 | * @param {object} unsubscribeOptions.invocationContext - passed to the onSuccess callback 1918 | or onFailure callback. 1919 | * @param {function} unsubscribeOptions.onSuccess - called when the unsubscribe acknowledgement has been received from the server. 1920 | * A single response object parameter is passed to the 1921 | * onSuccess callback containing the following fields: 1922 | *
    1923 | *
  1. invocationContext - if set in the unsubscribeOptions. 1924 | *
1925 | * @param {function} unsubscribeOptions.onFailure called when the unsubscribe request has failed or timed out. 1926 | * A single response object parameter is passed to the onFailure callback containing the following fields: 1927 | *
    1928 | *
  1. invocationContext - if set in the unsubscribeOptions. 1929 | *
  2. errorCode - a number indicating the nature of the error. 1930 | *
  3. errorMessage - text describing the error. 1931 | *
1932 | * @param {number} unsubscribeOptions.timeout - which, if present, determines the number of seconds 1933 | * after which the onFailure callback is called. The presence of 1934 | * a timeout does not prevent the onSuccess callback from being 1935 | * called when the unsubscribe completes 1936 | * @throws {InvalidState} if the client is not in connected state. 1937 | */ 1938 | this.unsubscribe = function(filter, unsubscribeOptions) { 1939 | if (typeof filter !== "string") 1940 | throw new Error("Invalid argument:" + filter); 1941 | unsubscribeOptions = unsubscribeOptions || {}; 1942 | validate(unsubscribeOptions, { 1943 | invocationContext: "object", 1944 | onSuccess: "function", 1945 | onFailure: "function", 1946 | timeout: "number" 1947 | }); 1948 | if (unsubscribeOptions.timeout && !unsubscribeOptions.onFailure) 1949 | throw new Error("unsubscribeOptions.timeout specified with no onFailure callback."); 1950 | client.unsubscribe(filter, unsubscribeOptions); 1951 | }; 1952 | 1953 | /** 1954 | * Send a message to the consumers of the destination in the Message. 1955 | * 1956 | * @name Paho.MQTT.Client#send 1957 | * @function 1958 | * @param {string|Paho.MQTT.Message} topic - mandatory The name of the destination to which the message is to be sent. 1959 | * - If it is the only parameter, used as Paho.MQTT.Message object. 1960 | * @param {String|ArrayBuffer} payload - The message data to be sent. 1961 | * @param {number} qos The Quality of Service used to deliver the message. 1962 | *
1963 | *
0 Best effort (default). 1964 | *
1 At least once. 1965 | *
2 Exactly once. 1966 | *
1967 | * @param {Boolean} retained If true, the message is to be retained by the server and delivered 1968 | * to both current and future subscriptions. 1969 | * If false the server only delivers the message to current subscribers, this is the default for new Messages. 1970 | * A received message has the retained boolean set to true if the message was published 1971 | * with the retained boolean set to true 1972 | * and the subscrption was made after the message has been published. 1973 | * @throws {InvalidState} if the client is not connected. 1974 | */ 1975 | this.send = function(topic, payload, qos, retained) { 1976 | var message; 1977 | 1978 | if (arguments.length == 0) { 1979 | throw new Error("Invalid argument." + "length"); 1980 | 1981 | } else if (arguments.length == 1) { 1982 | 1983 | if (!(topic instanceof Message) && (typeof topic !== "string")) 1984 | throw new Error("Invalid argument:" + typeof topic); 1985 | 1986 | message = topic; 1987 | if (typeof message.destinationName === "undefined") 1988 | throw new Error(format(ERROR.INVALID_ARGUMENT, [message.destinationName, "Message.destinationName"])); 1989 | client.send(message); 1990 | 1991 | } else { 1992 | //parameter checking in Message object 1993 | message = new Message(payload); 1994 | message.destinationName = topic; 1995 | if (arguments.length >= 3) 1996 | message.qos = qos; 1997 | if (arguments.length >= 4) 1998 | message.retained = retained; 1999 | client.send(message); 2000 | } 2001 | }; 2002 | 2003 | /** 2004 | * Normal disconnect of this Messaging client from its server. 2005 | * 2006 | * @name Paho.MQTT.Client#disconnect 2007 | * @function 2008 | * @throws {InvalidState} if the client is already disconnected. 2009 | */ 2010 | this.disconnect = function() { 2011 | client.disconnect(); 2012 | }; 2013 | 2014 | /** 2015 | * Get the contents of the trace log. 2016 | * 2017 | * @name Paho.MQTT.Client#getTraceLog 2018 | * @function 2019 | * @return {Object[]} tracebuffer containing the time ordered trace records. 2020 | */ 2021 | this.getTraceLog = function() { 2022 | return client.getTraceLog(); 2023 | } 2024 | 2025 | /** 2026 | * Start tracing. 2027 | * 2028 | * @name Paho.MQTT.Client#startTrace 2029 | * @function 2030 | */ 2031 | this.startTrace = function() { 2032 | client.startTrace(); 2033 | }; 2034 | 2035 | /** 2036 | * Stop tracing. 2037 | * 2038 | * @name Paho.MQTT.Client#stopTrace 2039 | * @function 2040 | */ 2041 | this.stopTrace = function() { 2042 | client.stopTrace(); 2043 | }; 2044 | 2045 | this.isConnected = function() { 2046 | return client.connected; 2047 | }; 2048 | }; 2049 | 2050 | Client.prototype = { 2051 | get host() { 2052 | return this._getHost(); 2053 | }, 2054 | set host(newHost) { 2055 | this._setHost(newHost); 2056 | }, 2057 | 2058 | get port() { 2059 | return this._getPort(); 2060 | }, 2061 | set port(newPort) { 2062 | this._setPort(newPort); 2063 | }, 2064 | 2065 | get path() { 2066 | return this._getPath(); 2067 | }, 2068 | set path(newPath) { 2069 | this._setPath(newPath); 2070 | }, 2071 | 2072 | get clientId() { 2073 | return this._getClientId(); 2074 | }, 2075 | set clientId(newClientId) { 2076 | this._setClientId(newClientId); 2077 | }, 2078 | 2079 | get onConnectionLost() { 2080 | return this._getOnConnectionLost(); 2081 | }, 2082 | set onConnectionLost(newOnConnectionLost) { 2083 | this._setOnConnectionLost(newOnConnectionLost); 2084 | }, 2085 | 2086 | get onMessageDelivered() { 2087 | return this._getOnMessageDelivered(); 2088 | }, 2089 | set onMessageDelivered(newOnMessageDelivered) { 2090 | this._setOnMessageDelivered(newOnMessageDelivered); 2091 | }, 2092 | 2093 | get onMessageArrived() { 2094 | return this._getOnMessageArrived(); 2095 | }, 2096 | set onMessageArrived(newOnMessageArrived) { 2097 | this._setOnMessageArrived(newOnMessageArrived); 2098 | }, 2099 | 2100 | get trace() { 2101 | return this._getTrace(); 2102 | }, 2103 | set trace(newTraceFunction) { 2104 | this._setTrace(newTraceFunction); 2105 | } 2106 | 2107 | }; 2108 | 2109 | /** 2110 | * An application message, sent or received. 2111 | *

2112 | * All attributes may be null, which implies the default values. 2113 | * 2114 | * @name Paho.MQTT.Message 2115 | * @constructor 2116 | * @param {String|ArrayBuffer} payload The message data to be sent. 2117 | *

2118 | * @property {string} payloadString read only The payload as a string if the payload consists of valid UTF-8 characters. 2119 | * @property {ArrayBuffer} payloadBytes read only The payload as an ArrayBuffer. 2120 | *

2121 | * @property {string} destinationName mandatory The name of the destination to which the message is to be sent 2122 | * (for messages about to be sent) or the name of the destination from which the message has been received. 2123 | * (for messages received by the onMessage function). 2124 | *

2125 | * @property {number} qos The Quality of Service used to deliver the message. 2126 | *

2127 | *
0 Best effort (default). 2128 | *
1 At least once. 2129 | *
2 Exactly once. 2130 | *
2131 | *

2132 | * @property {Boolean} retained If true, the message is to be retained by the server and delivered 2133 | * to both current and future subscriptions. 2134 | * If false the server only delivers the message to current subscribers, this is the default for new Messages. 2135 | * A received message has the retained boolean set to true if the message was published 2136 | * with the retained boolean set to true 2137 | * and the subscrption was made after the message has been published. 2138 | *

2139 | * @property {Boolean} duplicate read only If true, this message might be a duplicate of one which has already been received. 2140 | * This is only set on messages received from the server. 2141 | * 2142 | */ 2143 | var Message = function(newPayload) { 2144 | var payload; 2145 | if (typeof newPayload === "string" 2146 | || newPayload instanceof ArrayBuffer 2147 | || newPayload instanceof Int8Array 2148 | || newPayload instanceof Uint8Array 2149 | || newPayload instanceof Int16Array 2150 | || newPayload instanceof Uint16Array 2151 | || newPayload instanceof Int32Array 2152 | || newPayload instanceof Uint32Array 2153 | || newPayload instanceof Float32Array 2154 | || newPayload instanceof Float64Array 2155 | ) { 2156 | payload = newPayload; 2157 | } else { 2158 | throw (format(ERROR.INVALID_ARGUMENT, [newPayload, "newPayload"])); 2159 | } 2160 | 2161 | this._getPayloadString = function() { 2162 | if (typeof payload === "string") 2163 | return payload; 2164 | else 2165 | return parseUTF8(payload, 0, payload.length); 2166 | }; 2167 | 2168 | this._getPayloadBytes = function() { 2169 | if (typeof payload === "string") { 2170 | var buffer = new ArrayBuffer(UTF8Length(payload)); 2171 | var byteStream = new Uint8Array(buffer); 2172 | stringToUTF8(payload, byteStream, 0); 2173 | 2174 | return byteStream; 2175 | } else { 2176 | return payload; 2177 | } 2178 | ; 2179 | }; 2180 | 2181 | var destinationName = undefined; 2182 | this._getDestinationName = function() { 2183 | return destinationName; 2184 | }; 2185 | this._setDestinationName = function(newDestinationName) { 2186 | if (typeof newDestinationName === "string") 2187 | destinationName = newDestinationName; 2188 | else 2189 | throw new Error(format(ERROR.INVALID_ARGUMENT, [newDestinationName, "newDestinationName"])); 2190 | }; 2191 | 2192 | var qos = 0; 2193 | this._getQos = function() { 2194 | return qos; 2195 | }; 2196 | this._setQos = function(newQos) { 2197 | if (newQos === 0 || newQos === 1 || newQos === 2) 2198 | qos = newQos; 2199 | else 2200 | throw new Error("Invalid argument:" + newQos); 2201 | }; 2202 | 2203 | var retained = false; 2204 | this._getRetained = function() { 2205 | return retained; 2206 | }; 2207 | this._setRetained = function(newRetained) { 2208 | if (typeof newRetained === "boolean") 2209 | retained = newRetained; 2210 | else 2211 | throw new Error(format(ERROR.INVALID_ARGUMENT, [newRetained, "newRetained"])); 2212 | }; 2213 | 2214 | var duplicate = false; 2215 | this._getDuplicate = function() { 2216 | return duplicate; 2217 | }; 2218 | this._setDuplicate = function(newDuplicate) { 2219 | duplicate = newDuplicate; 2220 | }; 2221 | }; 2222 | 2223 | Message.prototype = { 2224 | get payloadString() { 2225 | return this._getPayloadString(); 2226 | }, 2227 | get payloadBytes() { 2228 | return this._getPayloadBytes(); 2229 | }, 2230 | 2231 | get destinationName() { 2232 | return this._getDestinationName(); 2233 | }, 2234 | set destinationName(newDestinationName) { 2235 | this._setDestinationName(newDestinationName); 2236 | }, 2237 | 2238 | get qos() { 2239 | return this._getQos(); 2240 | }, 2241 | set qos(newQos) { 2242 | this._setQos(newQos); 2243 | }, 2244 | 2245 | get retained() { 2246 | return this._getRetained(); 2247 | }, 2248 | set retained(newRetained) { 2249 | this._setRetained(newRetained); 2250 | }, 2251 | 2252 | get duplicate() { 2253 | return this._getDuplicate(); 2254 | }, 2255 | set duplicate(newDuplicate) { 2256 | this._setDuplicate(newDuplicate); 2257 | } 2258 | }; 2259 | 2260 | // Module contents. 2261 | return { 2262 | Client: Client, 2263 | Message: Message 2264 | }; 2265 | }) 2266 | (window); 2267 | 2268 | if(module && module.exports) module.exports=Paho; --------------------------------------------------------------------------------