Advertisement
Guest User

Untitled

a guest
Oct 28th, 2018
120
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 81.82 KB | None | 0 0
  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. * <p>
  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. * <ol>
  31. * <li>Connecting to and disconnecting from a server. The server is identified by its host name and port number.
  32. * <li>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. * <li>Subscribing to and receiving messages from MQTT Topics.
  35. * <li>Publishing messages to MQTT Topics.
  36. * </ol>
  37. * <p>
  38. * The API consists of two main objects:
  39. * <dl>
  40. * <dt><b>{@link Paho.MQTT.Client}</b></dt>
  41. * <dd>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.</dd>
  45. * <dt><b>{@link Paho.MQTT.Message}</b></dt>
  46. * <dd>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.</dd>
  49. * </dl>
  50. * <p>
  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. * <p>
  55. * Example:
  56. *
  57. * <code><pre>
  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. * </pre></code>
  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. {
  568. var charCode = input.charCodeAt(i);
  569. if (charCode > 0x7FF)
  570. {
  571. // Surrogate pair means its a 4 byte character
  572. if (0xD800 <= charCode && charCode <= 0xDBFF)
  573. {
  574. i++;
  575. output++;
  576. }
  577. output +=3;
  578. }
  579. else if (charCode > 0x7F)
  580. output +=2;
  581. else
  582. output++;
  583. }
  584. return output;
  585. }
  586.  
  587. /**
  588. * Takes a String and writes it into an array as UTF8 encoded bytes.
  589. * @private
  590. */
  591. function stringToUTF8(input, output, start) {
  592. var pos = start;
  593. for (var i = 0; i<input.length; i++) {
  594. var charCode = input.charCodeAt(i);
  595.  
  596. // Check for a surrogate pair.
  597. if (0xD800 <= charCode && charCode <= 0xDBFF) {
  598. var lowCharCode = input.charCodeAt(++i);
  599. if (isNaN(lowCharCode)) {
  600. throw new Error(format(ERROR.MALFORMED_UNICODE, [charCode, lowCharCode]));
  601. }
  602. charCode = ((charCode - 0xD800)<<10) + (lowCharCode - 0xDC00) + 0x10000;
  603.  
  604. }
  605.  
  606. if (charCode <= 0x7F) {
  607. output[pos++] = charCode;
  608. } else if (charCode <= 0x7FF) {
  609. output[pos++] = charCode>>6 & 0x1F | 0xC0;
  610. output[pos++] = charCode & 0x3F | 0x80;
  611. } else if (charCode <= 0xFFFF) {
  612. output[pos++] = charCode>>12 & 0x0F | 0xE0;
  613. output[pos++] = charCode>>6 & 0x3F | 0x80;
  614. output[pos++] = charCode & 0x3F | 0x80;
  615. } else {
  616. output[pos++] = charCode>>18 & 0x07 | 0xF0;
  617. output[pos++] = charCode>>12 & 0x3F | 0x80;
  618. output[pos++] = charCode>>6 & 0x3F | 0x80;
  619. output[pos++] = charCode & 0x3F | 0x80;
  620. };
  621. }
  622. return output;
  623. }
  624.  
  625. function parseUTF8(input, offset, length) {
  626. var output = "";
  627. var utf16;
  628. var pos = offset;
  629.  
  630. while (pos < offset+length)
  631. {
  632. var byte1 = input[pos++];
  633. if (byte1 < 128)
  634. utf16 = byte1;
  635. else
  636. {
  637. var byte2 = input[pos++]-128;
  638. if (byte2 < 0)
  639. throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16),""]));
  640. if (byte1 < 0xE0) // 2 byte character
  641. utf16 = 64*(byte1-0xC0) + byte2;
  642. else
  643. {
  644. var byte3 = input[pos++]-128;
  645. if (byte3 < 0)
  646. throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16)]));
  647. if (byte1 < 0xF0) // 3 byte character
  648. utf16 = 4096*(byte1-0xE0) + 64*byte2 + byte3;
  649. else
  650. {
  651. var byte4 = input[pos++]-128;
  652. if (byte4 < 0)
  653. throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)]));
  654. if (byte1 < 0xF8) // 4 byte character
  655. utf16 = 262144*(byte1-0xF0) + 4096*byte2 + 64*byte3 + byte4;
  656. else // longer encodings are not supported
  657. throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)]));
  658. }
  659. }
  660. }
  661.  
  662. if (utf16 > 0xFFFF) // 4 byte character - express as a surrogate pair
  663. {
  664. utf16 -= 0x10000;
  665. output += String.fromCharCode(0xD800 + (utf16 >> 10)); // lead character
  666. utf16 = 0xDC00 + (utf16 & 0x3FF); // trail character
  667. }
  668. output += String.fromCharCode(utf16);
  669. }
  670. return output;
  671. }
  672.  
  673. /**
  674. * Repeat keepalive requests, monitor responses.
  675. * @ignore
  676. */
  677. var Pinger = function(client, window, keepAliveInterval) {
  678. this._client = client;
  679. this._window = window;
  680. this._keepAliveInterval = keepAliveInterval*1000;
  681. this.isReset = false;
  682.  
  683. var pingReq = new WireMessage(MESSAGE_TYPE.PINGREQ).encode();
  684.  
  685. var doTimeout = function (pinger) {
  686. return function () {
  687. return doPing.apply(pinger);
  688. };
  689. };
  690.  
  691. /** @ignore */
  692. var doPing = function() {
  693. if (!this.isReset) {
  694. this._client._trace("Pinger.doPing", "Timed out");
  695. this._client._disconnected( ERROR.PING_TIMEOUT.code , format(ERROR.PING_TIMEOUT));
  696. } else {
  697. this.isReset = false;
  698. this._client._trace("Pinger.doPing", "send PINGREQ");
  699. this._client.socket.send(pingReq);
  700. this.timeout = this._window.setTimeout(doTimeout(this), this._keepAliveInterval);
  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) {subscribeOptions.onSuccess({invocationContext:subscribeOptions.invocationContext,grantedQos:grantedQos});};
  869. }
  870.  
  871. if (subscribeOptions.onFailure) {
  872. wireMessage.onFailure = function(errorCode) {subscribeOptions.onFailure({invocationContext:subscribeOptions.invocationContext,errorCode:errorCode});};
  873. }
  874.  
  875. if (subscribeOptions.timeout) {
  876. wireMessage.timeOut = new Timeout(this, window, subscribeOptions.timeout, subscribeOptions.onFailure
  877. , [{invocationContext:subscribeOptions.invocationContext,
  878. errorCode:ERROR.SUBSCRIBE_TIMEOUT.code,
  879. errorMessage:format(ERROR.SUBSCRIBE_TIMEOUT)}]);
  880. }
  881.  
  882. // All subscriptions return a SUBACK.
  883. this._requires_ack(wireMessage);
  884. this._schedule_message(wireMessage);
  885. };
  886.  
  887. /** @ignore */
  888. ClientImpl.prototype.unsubscribe = function(filter, unsubscribeOptions) {
  889. this._trace("Client.unsubscribe", filter, unsubscribeOptions);
  890.  
  891. if (!this.connected)
  892. throw new Error(format(ERROR.INVALID_STATE, ["not connected"]));
  893.  
  894. var wireMessage = new WireMessage(MESSAGE_TYPE.UNSUBSCRIBE);
  895. wireMessage.topics = [filter];
  896.  
  897. if (unsubscribeOptions.onSuccess) {
  898. wireMessage.callback = function() {unsubscribeOptions.onSuccess({invocationContext:unsubscribeOptions.invocationContext});};
  899. }
  900. if (unsubscribeOptions.timeout) {
  901. wireMessage.timeOut = new Timeout(this, window, unsubscribeOptions.timeout, unsubscribeOptions.onFailure
  902. , [{invocationContext:unsubscribeOptions.invocationContext,
  903. errorCode:ERROR.UNSUBSCRIBE_TIMEOUT.code,
  904. errorMessage:format(ERROR.UNSUBSCRIBE_TIMEOUT)}]);
  905. }
  906.  
  907. // All unsubscribes return a SUBACK.
  908. this._requires_ack(wireMessage);
  909. this._schedule_message(wireMessage);
  910. };
  911.  
  912. ClientImpl.prototype.send = function (message) {
  913. this._trace("Client.send", message);
  914.  
  915. if (!this.connected)
  916. throw new Error(format(ERROR.INVALID_STATE, ["not connected"]));
  917.  
  918. wireMessage = new WireMessage(MESSAGE_TYPE.PUBLISH);
  919. wireMessage.payloadMessage = message;
  920.  
  921. if (message.qos > 0)
  922. this._requires_ack(wireMessage);
  923. else if (this.onMessageDelivered)
  924. this._notify_msg_sent[wireMessage] = this.onMessageDelivered(wireMessage.payloadMessage);
  925. this._schedule_message(wireMessage);
  926. };
  927.  
  928. ClientImpl.prototype.disconnect = function () {
  929. this._trace("Client.disconnect");
  930.  
  931. if (!this.socket)
  932. throw new Error(format(ERROR.INVALID_STATE, ["not connecting or connected"]));
  933.  
  934. wireMessage = new WireMessage(MESSAGE_TYPE.DISCONNECT);
  935.  
  936. // Run the disconnected call back as soon as the message has been sent,
  937. // in case of a failure later on in the disconnect processing.
  938. // as a consequence, the _disconected call back may be run several times.
  939. this._notify_msg_sent[wireMessage] = scope(this._disconnected, this);
  940.  
  941. this._schedule_message(wireMessage);
  942. };
  943.  
  944. ClientImpl.prototype.getTraceLog = function () {
  945. if ( this._traceBuffer !== null ) {
  946. this._trace("Client.getTraceLog", new Date());
  947. this._trace("Client.getTraceLog in flight messages", this._sentMessages.length);
  948. for (var key in this._sentMessages)
  949. this._trace("_sentMessages ",key, this._sentMessages[key]);
  950. for (var key in this._receivedMessages)
  951. this._trace("_receivedMessages ",key, this._receivedMessages[key]);
  952.  
  953. return this._traceBuffer;
  954. }
  955. };
  956.  
  957. ClientImpl.prototype.startTrace = function () {
  958. if ( this._traceBuffer === null ) {
  959. this._traceBuffer = [];
  960. }
  961. this._trace("Client.startTrace", new Date(), version);
  962. };
  963.  
  964. ClientImpl.prototype.stopTrace = function () {
  965. delete this._traceBuffer;
  966. };
  967.  
  968. ClientImpl.prototype._doConnect = function (wsurl) {
  969. // When the socket is open, this client will send the CONNECT WireMessage using the saved parameters.
  970. if (this.connectOptions.useSSL) {
  971. var uriParts = wsurl.split(":");
  972. uriParts[0] = "wss";
  973. wsurl = uriParts.join(":");
  974. }
  975. this.connected = false;
  976. if (this.connectOptions.mqttVersion < 4) {
  977. this.socket = new WebSocket(wsurl, ["mqttv3.1"]);
  978. } else {
  979. this.socket = new WebSocket(wsurl, ["mqtt"]);
  980. }
  981. this.socket.binaryType = 'arraybuffer';
  982.  
  983. this.socket.onopen = scope(this._on_socket_open, this);
  984. this.socket.onmessage = scope(this._on_socket_message, this);
  985. this.socket.onerror = scope(this._on_socket_error, this);
  986. this.socket.onclose = scope(this._on_socket_close, this);
  987.  
  988. this.sendPinger = new Pinger(this, window, this.connectOptions.keepAliveInterval);
  989. this.receivePinger = new Pinger(this, window, this.connectOptions.keepAliveInterval);
  990.  
  991. this._connectTimeout = new Timeout(this, window, this.connectOptions.timeout, this._disconnected, [ERROR.CONNECT_TIMEOUT.code, format(ERROR.CONNECT_TIMEOUT)]);
  992. };
  993.  
  994.  
  995. // Schedule a new message to be sent over the WebSockets
  996. // connection. CONNECT messages cause WebSocket connection
  997. // to be started. All other messages are queued internally
  998. // until this has happened. When WS connection starts, process
  999. // all outstanding messages.
  1000. ClientImpl.prototype._schedule_message = function (message) {
  1001. this._msg_queue.push(message);
  1002. // Process outstanding messages in the queue if we have an open socket, and have received CONNACK.
  1003. if (this.connected) {
  1004. this._process_queue();
  1005. }
  1006. };
  1007.  
  1008. ClientImpl.prototype.store = function(prefix, wireMessage) {
  1009. var storedMessage = {type:wireMessage.type, messageIdentifier:wireMessage.messageIdentifier, version:1};
  1010.  
  1011. switch(wireMessage.type) {
  1012. case MESSAGE_TYPE.PUBLISH:
  1013. if(wireMessage.pubRecReceived)
  1014. storedMessage.pubRecReceived = true;
  1015.  
  1016. // Convert the payload to a hex string.
  1017. storedMessage.payloadMessage = {};
  1018. var hex = "";
  1019. var messageBytes = wireMessage.payloadMessage.payloadBytes;
  1020. for (var i=0; i<messageBytes.length; i++) {
  1021. if (messageBytes[i] <= 0xF)
  1022. hex = hex+"0"+messageBytes[i].toString(16);
  1023. else
  1024. hex = hex+messageBytes[i].toString(16);
  1025. }
  1026. storedMessage.payloadMessage.payloadHex = hex;
  1027.  
  1028. storedMessage.payloadMessage.qos = wireMessage.payloadMessage.qos;
  1029. storedMessage.payloadMessage.destinationName = wireMessage.payloadMessage.destinationName;
  1030. if (wireMessage.payloadMessage.duplicate)
  1031. storedMessage.payloadMessage.duplicate = true;
  1032. if (wireMessage.payloadMessage.retained)
  1033. storedMessage.payloadMessage.retained = true;
  1034.  
  1035. // Add a sequence number to sent messages.
  1036. if ( prefix.indexOf("Sent:") == 0 ) {
  1037. if ( wireMessage.sequence === undefined )
  1038. wireMessage.sequence = ++this._sequence;
  1039. storedMessage.sequence = wireMessage.sequence;
  1040. }
  1041. break;
  1042.  
  1043. default:
  1044. throw Error(format(ERROR.INVALID_STORED_DATA, [key, storedMessage]));
  1045. }
  1046. localStorage.setItem(prefix+this._localKey+wireMessage.messageIdentifier, JSON.stringify(storedMessage));
  1047. };
  1048.  
  1049. ClientImpl.prototype.restore = function(key) {
  1050. var value = localStorage.getItem(key);
  1051. var storedMessage = JSON.parse(value);
  1052.  
  1053. var wireMessage = new WireMessage(storedMessage.type, storedMessage);
  1054.  
  1055. switch(storedMessage.type) {
  1056. case MESSAGE_TYPE.PUBLISH:
  1057. // Replace the payload message with a Message object.
  1058. var hex = storedMessage.payloadMessage.payloadHex;
  1059. var buffer = new ArrayBuffer((hex.length)/2);
  1060. var byteStream = new Uint8Array(buffer);
  1061. var i = 0;
  1062. while (hex.length >= 2) {
  1063. var x = parseInt(hex.substring(0, 2), 16);
  1064. hex = hex.substring(2, hex.length);
  1065. byteStream[i++] = x;
  1066. }
  1067. var payloadMessage = new Paho.MQTT.Message(byteStream);
  1068.  
  1069. payloadMessage.qos = storedMessage.payloadMessage.qos;
  1070. payloadMessage.destinationName = storedMessage.payloadMessage.destinationName;
  1071. if (storedMessage.payloadMessage.duplicate)
  1072. payloadMessage.duplicate = true;
  1073. if (storedMessage.payloadMessage.retained)
  1074. payloadMessage.retained = true;
  1075. wireMessage.payloadMessage = payloadMessage;
  1076.  
  1077. break;
  1078.  
  1079. default:
  1080. throw Error(format(ERROR.INVALID_STORED_DATA, [key, value]));
  1081. }
  1082.  
  1083. if (key.indexOf("Sent:"+this._localKey) == 0) {
  1084. wireMessage.payloadMessage.duplicate = true;
  1085. this._sentMessages[wireMessage.messageIdentifier] = wireMessage;
  1086. } else if (key.indexOf("Received:"+this._localKey) == 0) {
  1087. this._receivedMessages[wireMessage.messageIdentifier] = wireMessage;
  1088. }
  1089. };
  1090.  
  1091. ClientImpl.prototype._process_queue = function () {
  1092. var message = null;
  1093. // Process messages in order they were added
  1094. var fifo = this._msg_queue.reverse();
  1095.  
  1096. // Send all queued messages down socket connection
  1097. while ((message = fifo.pop())) {
  1098. this._socket_send(message);
  1099. // Notify listeners that message was successfully sent
  1100. if (this._notify_msg_sent[message]) {
  1101. this._notify_msg_sent[message]();
  1102. delete this._notify_msg_sent[message];
  1103. }
  1104. }
  1105. };
  1106.  
  1107. /**
  1108. * Expect an ACK response for this message. Add message to the set of in progress
  1109. * messages and set an unused identifier in this message.
  1110. * @ignore
  1111. */
  1112. ClientImpl.prototype._requires_ack = function (wireMessage) {
  1113. var messageCount = Object.keys(this._sentMessages).length;
  1114. if (messageCount > this.maxMessageIdentifier)
  1115. throw Error ("Too many messages:"+messageCount);
  1116.  
  1117. while(this._sentMessages[this._message_identifier] !== undefined) {
  1118. this._message_identifier++;
  1119. }
  1120. wireMessage.messageIdentifier = this._message_identifier;
  1121. this._sentMessages[wireMessage.messageIdentifier] = wireMessage;
  1122. if (wireMessage.type === MESSAGE_TYPE.PUBLISH) {
  1123. this.store("Sent:", wireMessage);
  1124. }
  1125. if (this._message_identifier === this.maxMessageIdentifier) {
  1126. this._message_identifier = 1;
  1127. }
  1128. };
  1129.  
  1130. /**
  1131. * Called when the underlying websocket has been opened.
  1132. * @ignore
  1133. */
  1134. ClientImpl.prototype._on_socket_open = function () {
  1135. // Create the CONNECT message object.
  1136. var wireMessage = new WireMessage(MESSAGE_TYPE.CONNECT, this.connectOptions);
  1137. wireMessage.clientId = this.clientId;
  1138. this._socket_send(wireMessage);
  1139. };
  1140.  
  1141. /**
  1142. * Called when the underlying websocket has received a complete packet.
  1143. * @ignore
  1144. */
  1145. ClientImpl.prototype._on_socket_message = function (event) {
  1146. this._trace("Client._on_socket_message", event.data);
  1147. // Reset the receive ping timer, we now have evidence the server is alive.
  1148. this.receivePinger.reset();
  1149. var messages = this._deframeMessages(event.data);
  1150. for (var i = 0; i < messages.length; i+=1) {
  1151. this._handleMessage(messages[i]);
  1152. }
  1153. }
  1154.  
  1155. ClientImpl.prototype._deframeMessages = function(data) {
  1156. var byteArray = new Uint8Array(data);
  1157. if (this.receiveBuffer) {
  1158. var newData = new Uint8Array(this.receiveBuffer.length+byteArray.length);
  1159. newData.set(this.receiveBuffer);
  1160. newData.set(byteArray,this.receiveBuffer.length);
  1161. byteArray = newData;
  1162. delete this.receiveBuffer;
  1163. }
  1164. try {
  1165. var offset = 0;
  1166. var messages = [];
  1167. while(offset < byteArray.length) {
  1168. var result = decodeMessage(byteArray,offset);
  1169. var wireMessage = result[0];
  1170. offset = result[1];
  1171. if (wireMessage !== null) {
  1172. messages.push(wireMessage);
  1173. } else {
  1174. break;
  1175. }
  1176. }
  1177. if (offset < byteArray.length) {
  1178. this.receiveBuffer = byteArray.subarray(offset);
  1179. }
  1180. } catch (error) {
  1181. this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()]));
  1182. return;
  1183. }
  1184. return messages;
  1185. }
  1186.  
  1187. ClientImpl.prototype._handleMessage = function(wireMessage) {
  1188.  
  1189. this._trace("Client._handleMessage", wireMessage);
  1190.  
  1191. try {
  1192. switch(wireMessage.type) {
  1193. case MESSAGE_TYPE.CONNACK:
  1194. this._connectTimeout.cancel();
  1195.  
  1196. // If we have started using clean session then clear up the local state.
  1197. if (this.connectOptions.cleanSession) {
  1198. for (var key in this._sentMessages) {
  1199. var sentMessage = this._sentMessages[key];
  1200. localStorage.removeItem("Sent:"+this._localKey+sentMessage.messageIdentifier);
  1201. }
  1202. this._sentMessages = {};
  1203.  
  1204. for (var key in this._receivedMessages) {
  1205. var receivedMessage = this._receivedMessages[key];
  1206. localStorage.removeItem("Received:"+this._localKey+receivedMessage.messageIdentifier);
  1207. }
  1208. this._receivedMessages = {};
  1209. }
  1210. // Client connected and ready for business.
  1211. if (wireMessage.returnCode === 0) {
  1212. this.connected = true;
  1213. // Jump to the end of the list of uris and stop looking for a good host.
  1214. if (this.connectOptions.uris)
  1215. this.hostIndex = this.connectOptions.uris.length;
  1216. } else {
  1217. this._disconnected(ERROR.CONNACK_RETURNCODE.code , format(ERROR.CONNACK_RETURNCODE, [wireMessage.returnCode, CONNACK_RC[wireMessage.returnCode]]));
  1218. break;
  1219. }
  1220.  
  1221. // Resend messages.
  1222. var sequencedMessages = new Array();
  1223. for (var msgId in this._sentMessages) {
  1224. if (this._sentMessages.hasOwnProperty(msgId))
  1225. sequencedMessages.push(this._sentMessages[msgId]);
  1226. }
  1227.  
  1228. // Sort sentMessages into the original sent order.
  1229. var sequencedMessages = sequencedMessages.sort(function(a,b) {return a.sequence - b.sequence;} );
  1230. for (var i=0, len=sequencedMessages.length; i<len; i++) {
  1231. var sentMessage = sequencedMessages[i];
  1232. if (sentMessage.type == MESSAGE_TYPE.PUBLISH && sentMessage.pubRecReceived) {
  1233. var pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier:sentMessage.messageIdentifier});
  1234. this._schedule_message(pubRelMessage);
  1235. } else {
  1236. this._schedule_message(sentMessage);
  1237. };
  1238. }
  1239.  
  1240. // Execute the connectOptions.onSuccess callback if there is one.
  1241. if (this.connectOptions.onSuccess) {
  1242. this.connectOptions.onSuccess({invocationContext:this.connectOptions.invocationContext});
  1243. }
  1244.  
  1245. // Process all queued messages now that the connection is established.
  1246. this._process_queue();
  1247. break;
  1248.  
  1249. case MESSAGE_TYPE.PUBLISH:
  1250. this._receivePublish(wireMessage);
  1251. break;
  1252.  
  1253. case MESSAGE_TYPE.PUBACK:
  1254. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1255. // If this is a re flow of a PUBACK after we have restarted receivedMessage will not exist.
  1256. if (sentMessage) {
  1257. delete this._sentMessages[wireMessage.messageIdentifier];
  1258. localStorage.removeItem("Sent:"+this._localKey+wireMessage.messageIdentifier);
  1259. if (this.onMessageDelivered)
  1260. this.onMessageDelivered(sentMessage.payloadMessage);
  1261. }
  1262. break;
  1263.  
  1264. case MESSAGE_TYPE.PUBREC:
  1265. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1266. // If this is a re flow of a PUBREC after we have restarted receivedMessage will not exist.
  1267. if (sentMessage) {
  1268. sentMessage.pubRecReceived = true;
  1269. var pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier:wireMessage.messageIdentifier});
  1270. this.store("Sent:", sentMessage);
  1271. this._schedule_message(pubRelMessage);
  1272. }
  1273. break;
  1274.  
  1275. case MESSAGE_TYPE.PUBREL:
  1276. var receivedMessage = this._receivedMessages[wireMessage.messageIdentifier];
  1277. localStorage.removeItem("Received:"+this._localKey+wireMessage.messageIdentifier);
  1278. // If this is a re flow of a PUBREL after we have restarted receivedMessage will not exist.
  1279. if (receivedMessage) {
  1280. this._receiveMessage(receivedMessage);
  1281. delete this._receivedMessages[wireMessage.messageIdentifier];
  1282. }
  1283. // Always flow PubComp, we may have previously flowed PubComp but the server lost it and restarted.
  1284. var pubCompMessage = new WireMessage(MESSAGE_TYPE.PUBCOMP, {messageIdentifier:wireMessage.messageIdentifier});
  1285. this._schedule_message(pubCompMessage);
  1286. break;
  1287.  
  1288. case MESSAGE_TYPE.PUBCOMP:
  1289. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1290. delete this._sentMessages[wireMessage.messageIdentifier];
  1291. localStorage.removeItem("Sent:"+this._localKey+wireMessage.messageIdentifier);
  1292. if (this.onMessageDelivered)
  1293. this.onMessageDelivered(sentMessage.payloadMessage);
  1294. break;
  1295.  
  1296. case MESSAGE_TYPE.SUBACK:
  1297. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1298. if (sentMessage) {
  1299. if(sentMessage.timeOut)
  1300. sentMessage.timeOut.cancel();
  1301. // This will need to be fixed when we add multiple topic support
  1302. if (wireMessage.returnCode[0] === 0x80) {
  1303. if (sentMessage.onFailure) {
  1304. sentMessage.onFailure(wireMessage.returnCode);
  1305. }
  1306. } else if (sentMessage.onSuccess) {
  1307. sentMessage.onSuccess(wireMessage.returnCode);
  1308. }
  1309. delete this._sentMessages[wireMessage.messageIdentifier];
  1310. }
  1311. break;
  1312.  
  1313. case MESSAGE_TYPE.UNSUBACK:
  1314. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1315. if (sentMessage) {
  1316. if (sentMessage.timeOut)
  1317. sentMessage.timeOut.cancel();
  1318. if (sentMessage.callback) {
  1319. sentMessage.callback();
  1320. }
  1321. delete this._sentMessages[wireMessage.messageIdentifier];
  1322. }
  1323.  
  1324. break;
  1325.  
  1326. case MESSAGE_TYPE.PINGRESP:
  1327. /* The sendPinger or receivePinger may have sent a ping, the receivePinger has already been reset. */
  1328. this.sendPinger.reset();
  1329. break;
  1330.  
  1331. case MESSAGE_TYPE.DISCONNECT:
  1332. // Clients do not expect to receive disconnect packets.
  1333. this._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code , format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type]));
  1334. break;
  1335.  
  1336. default:
  1337. this._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code , format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type]));
  1338. };
  1339. } catch (error) {
  1340. this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()]));
  1341. return;
  1342. }
  1343. };
  1344.  
  1345. /** @ignore */
  1346. ClientImpl.prototype._on_socket_error = function (error) {
  1347. this._disconnected(ERROR.SOCKET_ERROR.code , format(ERROR.SOCKET_ERROR, [error.data]));
  1348. };
  1349.  
  1350. /** @ignore */
  1351. ClientImpl.prototype._on_socket_close = function () {
  1352. this._disconnected(ERROR.SOCKET_CLOSE.code , format(ERROR.SOCKET_CLOSE));
  1353. };
  1354.  
  1355. /** @ignore */
  1356. ClientImpl.prototype._socket_send = function (wireMessage) {
  1357.  
  1358. if (wireMessage.type == 1) {
  1359. var wireMessageMasked = this._traceMask(wireMessage, "password");
  1360. this._trace("Client._socket_send", wireMessageMasked);
  1361. }
  1362. else this._trace("Client._socket_send", wireMessage);
  1363.  
  1364. this.socket.send(wireMessage.encode());
  1365. /* We have proved to the server we are alive. */
  1366. this.sendPinger.reset();
  1367. };
  1368.  
  1369. /** @ignore */
  1370. ClientImpl.prototype._receivePublish = function (wireMessage) {
  1371. switch(wireMessage.payloadMessage.qos) {
  1372. case "undefined":
  1373. case 0:
  1374. this._receiveMessage(wireMessage);
  1375. break;
  1376.  
  1377. case 1:
  1378. var pubAckMessage = new WireMessage(MESSAGE_TYPE.PUBACK, {messageIdentifier:wireMessage.messageIdentifier});
  1379. this._schedule_message(pubAckMessage);
  1380. this._receiveMessage(wireMessage);
  1381. break;
  1382.  
  1383. case 2:
  1384. this._receivedMessages[wireMessage.messageIdentifier] = wireMessage;
  1385. this.store("Received:", wireMessage);
  1386. var pubRecMessage = new WireMessage(MESSAGE_TYPE.PUBREC, {messageIdentifier:wireMessage.messageIdentifier});
  1387. this._schedule_message(pubRecMessage);
  1388.  
  1389. break;
  1390.  
  1391. default:
  1392. throw Error("Invaild qos="+wireMmessage.payloadMessage.qos);
  1393. };
  1394. };
  1395.  
  1396. /** @ignore */
  1397. ClientImpl.prototype._receiveMessage = function (wireMessage) {
  1398. if (this.onMessageArrived) {
  1399. this.onMessageArrived(wireMessage.payloadMessage);
  1400. }
  1401. };
  1402.  
  1403. /**
  1404. * Client has disconnected either at its own request or because the server
  1405. * or network disconnected it. Remove all non-durable state.
  1406. * @param {errorCode} [number] the error number.
  1407. * @param {errorText} [string] the error text.
  1408. * @ignore
  1409. */
  1410. ClientImpl.prototype._disconnected = function (errorCode, errorText) {
  1411. this._trace("Client._disconnected", errorCode, errorText);
  1412.  
  1413. this.sendPinger.cancel();
  1414. this.receivePinger.cancel();
  1415. if (this._connectTimeout)
  1416. this._connectTimeout.cancel();
  1417. // Clear message buffers.
  1418. this._msg_queue = [];
  1419. this._notify_msg_sent = {};
  1420.  
  1421. if (this.socket) {
  1422. // Cancel all socket callbacks so that they cannot be driven again by this socket.
  1423. this.socket.onopen = null;
  1424. this.socket.onmessage = null;
  1425. this.socket.onerror = null;
  1426. this.socket.onclose = null;
  1427. if (this.socket.readyState === 1)
  1428. this.socket.close();
  1429. delete this.socket;
  1430. }
  1431.  
  1432. if (this.connectOptions.uris && this.hostIndex < this.connectOptions.uris.length-1) {
  1433. // Try the next host.
  1434. this.hostIndex++;
  1435. this._doConnect(this.connectOptions.uris[this.hostIndex]);
  1436.  
  1437. } else {
  1438.  
  1439. if (errorCode === undefined) {
  1440. errorCode = ERROR.OK.code;
  1441. errorText = format(ERROR.OK);
  1442. }
  1443.  
  1444. // Run any application callbacks last as they may attempt to reconnect and hence create a new socket.
  1445. if (this.connected) {
  1446. this.connected = false;
  1447. // Execute the connectionLostCallback if there is one, and we were connected.
  1448. if (this.onConnectionLost)
  1449. this.onConnectionLost({errorCode:errorCode, errorMessage:errorText});
  1450. } else {
  1451. // Otherwise we never had a connection, so indicate that the connect has failed.
  1452. if (this.connectOptions.mqttVersion === 4 && this.connectOptions.mqttVersionExplicit === false) {
  1453. this._trace("Failed to connect V4, dropping back to V3")
  1454. this.connectOptions.mqttVersion = 3;
  1455. if (this.connectOptions.uris) {
  1456. this.hostIndex = 0;
  1457. this._doConnect(this.connectOptions.uris[0]);
  1458. } else {
  1459. this._doConnect(this.uri);
  1460. }
  1461. } else if(this.connectOptions.onFailure) {
  1462. this.connectOptions.onFailure({invocationContext:this.connectOptions.invocationContext, errorCode:errorCode, errorMessage:errorText});
  1463. }
  1464. }
  1465. }
  1466. };
  1467.  
  1468. /** @ignore */
  1469. ClientImpl.prototype._trace = function () {
  1470. // Pass trace message back to client's callback function
  1471. if (this.traceFunction) {
  1472. for (var i in arguments)
  1473. {
  1474. if (typeof arguments[i] !== "undefined")
  1475. arguments[i] = JSON.stringify(arguments[i]);
  1476. }
  1477. var record = Array.prototype.slice.call(arguments).join("");
  1478. this.traceFunction ({severity: "Debug", message: record });
  1479. }
  1480.  
  1481. //buffer style trace
  1482. if ( this._traceBuffer !== null ) {
  1483. for (var i = 0, max = arguments.length; i < max; i++) {
  1484. if ( this._traceBuffer.length == this._MAX_TRACE_ENTRIES ) {
  1485. this._traceBuffer.shift();
  1486. }
  1487. if (i === 0) this._traceBuffer.push(arguments[i]);
  1488. else if (typeof arguments[i] === "undefined" ) this._traceBuffer.push(arguments[i]);
  1489. else this._traceBuffer.push(" "+JSON.stringify(arguments[i]));
  1490. };
  1491. };
  1492. };
  1493.  
  1494. /** @ignore */
  1495. ClientImpl.prototype._traceMask = function (traceObject, masked) {
  1496. var traceObjectMasked = {};
  1497. for (var attr in traceObject) {
  1498. if (traceObject.hasOwnProperty(attr)) {
  1499. if (attr == masked)
  1500. traceObjectMasked[attr] = "******";
  1501. else
  1502. traceObjectMasked[attr] = traceObject[attr];
  1503. }
  1504. }
  1505. return traceObjectMasked;
  1506. };
  1507.  
  1508. // ------------------------------------------------------------------------
  1509. // Public Programming interface.
  1510. // ------------------------------------------------------------------------
  1511.  
  1512. /**
  1513. * The JavaScript application communicates to the server using a {@link Paho.MQTT.Client} object.
  1514. * <p>
  1515. * Most applications will create just one Client object and then call its connect() method,
  1516. * however applications can create more than one Client object if they wish.
  1517. * In this case the combination of host, port and clientId attributes must be different for each Client object.
  1518. * <p>
  1519. * The send, subscribe and unsubscribe methods are implemented as asynchronous JavaScript methods
  1520. * (even though the underlying protocol exchange might be synchronous in nature).
  1521. * This means they signal their completion by calling back to the application,
  1522. * via Success or Failure callback functions provided by the application on the method in question.
  1523. * Such callbacks are called at most once per method invocation and do not persist beyond the lifetime
  1524. * of the script that made the invocation.
  1525. * <p>
  1526. * In contrast there are some callback functions, most notably <i>onMessageArrived</i>,
  1527. * that are defined on the {@link Paho.MQTT.Client} object.
  1528. * These may get called multiple times, and aren't directly related to specific method invocations made by the client.
  1529. *
  1530. * @name Paho.MQTT.Client
  1531. *
  1532. * @constructor
  1533. *
  1534. * @param {string} host - the address of the messaging server, as a fully qualified WebSocket URI, as a DNS name or dotted decimal IP address.
  1535. * @param {number} port - the port number to connect to - only required if host is not a URI
  1536. * @param {string} path - the path on the host to connect to - only used if host is not a URI. Default: '/mqtt'.
  1537. * @param {string} clientId - the Messaging client identifier, between 1 and 23 characters in length.
  1538. *
  1539. * @property {string} host - <i>read only</i> the server's DNS hostname or dotted decimal IP address.
  1540. * @property {number} port - <i>read only</i> the server's port.
  1541. * @property {string} path - <i>read only</i> the server's path.
  1542. * @property {string} clientId - <i>read only</i> used when connecting to the server.
  1543. * @property {function} onConnectionLost - called when a connection has been lost.
  1544. * after a connect() method has succeeded.
  1545. * Establish the call back used when a connection has been lost. The connection may be
  1546. * lost because the client initiates a disconnect or because the server or network
  1547. * cause the client to be disconnected. The disconnect call back may be called without
  1548. * the connectionComplete call back being invoked if, for example the client fails to
  1549. * connect.
  1550. * A single response object parameter is passed to the onConnectionLost callback containing the following fields:
  1551. * <ol>
  1552. * <li>errorCode
  1553. * <li>errorMessage
  1554. * </ol>
  1555. * @property {function} onMessageDelivered called when a message has been delivered.
  1556. * All processing that this Client will ever do has been completed. So, for example,
  1557. * in the case of a Qos=2 message sent by this client, the PubComp flow has been received from the server
  1558. * and the message has been removed from persistent storage before this callback is invoked.
  1559. * Parameters passed to the onMessageDelivered callback are:
  1560. * <ol>
  1561. * <li>{@link Paho.MQTT.Message} that was delivered.
  1562. * </ol>
  1563. * @property {function} onMessageArrived called when a message has arrived in this Paho.MQTT.client.
  1564. * Parameters passed to the onMessageArrived callback are:
  1565. * <ol>
  1566. * <li>{@link Paho.MQTT.Message} that has arrived.
  1567. * </ol>
  1568. */
  1569. var Client = function (host, port, path, clientId) {
  1570.  
  1571. var uri;
  1572.  
  1573. if (typeof host !== "string")
  1574. throw new Error(format(ERROR.INVALID_TYPE, [typeof host, "host"]));
  1575.  
  1576. if (arguments.length == 2) {
  1577. // host: must be full ws:// uri
  1578. // port: clientId
  1579. clientId = port;
  1580. uri = host;
  1581. var match = uri.match(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/);
  1582. if (match) {
  1583. host = match[4]||match[2];
  1584. port = parseInt(match[7]);
  1585. path = match[8];
  1586. } else {
  1587. throw new Error(format(ERROR.INVALID_ARGUMENT,[host,"host"]));
  1588. }
  1589. } else {
  1590. if (arguments.length == 3) {
  1591. clientId = path;
  1592. path = "/mqtt";
  1593. }
  1594. if (typeof port !== "number" || port < 0)
  1595. throw new Error(format(ERROR.INVALID_TYPE, [typeof port, "port"]));
  1596. if (typeof path !== "string")
  1597. throw new Error(format(ERROR.INVALID_TYPE, [typeof path, "path"]));
  1598.  
  1599. var ipv6AddSBracket = (host.indexOf(":") != -1 && host.slice(0,1) != "[" && host.slice(-1) != "]");
  1600. uri = "ws://"+(ipv6AddSBracket?"["+host+"]":host)+":"+port+path;
  1601. }
  1602.  
  1603. var clientIdLength = 0;
  1604. for (var i = 0; i<clientId.length; i++) {
  1605. var charCode = clientId.charCodeAt(i);
  1606. if (0xD800 <= charCode && charCode <= 0xDBFF) {
  1607. i++; // Surrogate pair.
  1608. }
  1609. clientIdLength++;
  1610. }
  1611. if (typeof clientId !== "string" || clientIdLength > 65535)
  1612. throw new Error(format(ERROR.INVALID_ARGUMENT, [clientId, "clientId"]));
  1613.  
  1614. var client = new ClientImpl(uri, host, port, path, clientId);
  1615. this._getHost = function() { return host; };
  1616. this._setHost = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1617.  
  1618. this._getPort = function() { return port; };
  1619. this._setPort = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1620.  
  1621. this._getPath = function() { return path; };
  1622. this._setPath = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1623.  
  1624. this._getURI = function() { return uri; };
  1625. this._setURI = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1626.  
  1627. this._getClientId = function() { return client.clientId; };
  1628. this._setClientId = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1629.  
  1630. this._getOnConnectionLost = function() { return client.onConnectionLost; };
  1631. this._setOnConnectionLost = function(newOnConnectionLost) {
  1632. if (typeof newOnConnectionLost === "function")
  1633. client.onConnectionLost = newOnConnectionLost;
  1634. else
  1635. throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnectionLost, "onConnectionLost"]));
  1636. };
  1637.  
  1638. this._getOnMessageDelivered = function() { return client.onMessageDelivered; };
  1639. this._setOnMessageDelivered = function(newOnMessageDelivered) {
  1640. if (typeof newOnMessageDelivered === "function")
  1641. client.onMessageDelivered = newOnMessageDelivered;
  1642. else
  1643. throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageDelivered, "onMessageDelivered"]));
  1644. };
  1645.  
  1646. this._getOnMessageArrived = function() { return client.onMessageArrived; };
  1647. this._setOnMessageArrived = function(newOnMessageArrived) {
  1648. if (typeof newOnMessageArrived === "function")
  1649. client.onMessageArrived = newOnMessageArrived;
  1650. else
  1651. throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageArrived, "onMessageArrived"]));
  1652. };
  1653.  
  1654. this._getTrace = function() { return client.traceFunction; };
  1655. this._setTrace = function(trace) {
  1656. if(typeof trace === "function"){
  1657. client.traceFunction = trace;
  1658. }else{
  1659. throw new Error(format(ERROR.INVALID_TYPE, [typeof trace, "onTrace"]));
  1660. }
  1661. };
  1662.  
  1663. /**
  1664. * Connect this Messaging client to its server.
  1665. *
  1666. * @name Paho.MQTT.Client#connect
  1667. * @function
  1668. * @param {Object} connectOptions - attributes used with the connection.
  1669. * @param {number} connectOptions.timeout - If the connect has not succeeded within this
  1670. * number of seconds, it is deemed to have failed.
  1671. * The default is 30 seconds.
  1672. * @param {string} connectOptions.userName - Authentication username for this connection.
  1673. * @param {string} connectOptions.password - Authentication password for this connection.
  1674. * @param {Paho.MQTT.Message} connectOptions.willMessage - sent by the server when the client
  1675. * disconnects abnormally.
  1676. * @param {Number} connectOptions.keepAliveInterval - the server disconnects this client if
  1677. * there is no activity for this number of seconds.
  1678. * The default value of 60 seconds is assumed if not set.
  1679. * @param {boolean} connectOptions.cleanSession - if true(default) the client and server
  1680. * persistent state is deleted on successful connect.
  1681. * @param {boolean} connectOptions.useSSL - if present and true, use an SSL Websocket connection.
  1682. * @param {object} connectOptions.invocationContext - passed to the onSuccess callback or onFailure callback.
  1683. * @param {function} connectOptions.onSuccess - called when the connect acknowledgement
  1684. * has been received from the server.
  1685. * A single response object parameter is passed to the onSuccess callback containing the following fields:
  1686. * <ol>
  1687. * <li>invocationContext as passed in to the onSuccess method in the connectOptions.
  1688. * </ol>
  1689. * @config {function} [onFailure] called when the connect request has failed or timed out.
  1690. * A single response object parameter is passed to the onFailure callback containing the following fields:
  1691. * <ol>
  1692. * <li>invocationContext as passed in to the onFailure method in the connectOptions.
  1693. * <li>errorCode a number indicating the nature of the error.
  1694. * <li>errorMessage text describing the error.
  1695. * </ol>
  1696. * @config {Array} [hosts] If present this contains either a set of hostnames or fully qualified
  1697. * WebSocket URIs (ws://example.com:1883/mqtt), that are tried in order in place
  1698. * of the host and port paramater on the construtor. The hosts are tried one at at time in order until
  1699. * one of then succeeds.
  1700. * @config {Array} [ports] If present the set of ports matching the hosts. If hosts contains URIs, this property
  1701. * is not used.
  1702. * @throws {InvalidState} if the client is not in disconnected state. The client must have received connectionLost
  1703. * or disconnected before calling connect for a second or subsequent time.
  1704. */
  1705. this.connect = function (connectOptions) {
  1706. connectOptions = connectOptions || {} ;
  1707. validate(connectOptions, {timeout:"number",
  1708. userName:"string",
  1709. password:"string",
  1710. willMessage:"object",
  1711. keepAliveInterval:"number",
  1712. cleanSession:"boolean",
  1713. useSSL:"boolean",
  1714. invocationContext:"object",
  1715. onSuccess:"function",
  1716. onFailure:"function",
  1717. hosts:"object",
  1718. ports:"object",
  1719. mqttVersion:"number"});
  1720.  
  1721. // If no keep alive interval is set, assume 60 seconds.
  1722. if (connectOptions.keepAliveInterval === undefined)
  1723. connectOptions.keepAliveInterval = 60;
  1724.  
  1725. if (connectOptions.mqttVersion > 4 || connectOptions.mqttVersion < 3) {
  1726. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.mqttVersion, "connectOptions.mqttVersion"]));
  1727. }
  1728.  
  1729. if (connectOptions.mqttVersion === undefined) {
  1730. connectOptions.mqttVersionExplicit = false;
  1731. connectOptions.mqttVersion = 4;
  1732. } else {
  1733. connectOptions.mqttVersionExplicit = true;
  1734. }
  1735.  
  1736. //Check that if password is set, so is username
  1737. if (connectOptions.password === undefined && connectOptions.userName !== undefined)
  1738. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.password, "connectOptions.password"]))
  1739.  
  1740. if (connectOptions.willMessage) {
  1741. if (!(connectOptions.willMessage instanceof Message))
  1742. throw new Error(format(ERROR.INVALID_TYPE, [connectOptions.willMessage, "connectOptions.willMessage"]));
  1743. // The will message must have a payload that can be represented as a string.
  1744. // Cause the willMessage to throw an exception if this is not the case.
  1745. connectOptions.willMessage.stringPayload;
  1746.  
  1747. if (typeof connectOptions.willMessage.destinationName === "undefined")
  1748. throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.willMessage.destinationName, "connectOptions.willMessage.destinationName"]));
  1749. }
  1750. if (typeof connectOptions.cleanSession === "undefined")
  1751. connectOptions.cleanSession = true;
  1752. if (connectOptions.hosts) {
  1753.  
  1754. if (!(connectOptions.hosts instanceof Array) )
  1755. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"]));
  1756. if (connectOptions.hosts.length <1 )
  1757. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"]));
  1758.  
  1759. var usingURIs = false;
  1760. for (var i = 0; i<connectOptions.hosts.length; i++) {
  1761. if (typeof connectOptions.hosts[i] !== "string")
  1762. throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.hosts[i], "connectOptions.hosts["+i+"]"]));
  1763. if (/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/.test(connectOptions.hosts[i])) {
  1764. if (i == 0) {
  1765. usingURIs = true;
  1766. } else if (!usingURIs) {
  1767. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], "connectOptions.hosts["+i+"]"]));
  1768. }
  1769. } else if (usingURIs) {
  1770. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], "connectOptions.hosts["+i+"]"]));
  1771. }
  1772. }
  1773.  
  1774. if (!usingURIs) {
  1775. if (!connectOptions.ports)
  1776. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"]));
  1777. if (!(connectOptions.ports instanceof Array) )
  1778. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"]));
  1779. if (connectOptions.hosts.length != connectOptions.ports.length)
  1780. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"]));
  1781.  
  1782. connectOptions.uris = [];
  1783.  
  1784. for (var i = 0; i<connectOptions.hosts.length; i++) {
  1785. if (typeof connectOptions.ports[i] !== "number" || connectOptions.ports[i] < 0)
  1786. throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.ports[i], "connectOptions.ports["+i+"]"]));
  1787. var host = connectOptions.hosts[i];
  1788. var port = connectOptions.ports[i];
  1789.  
  1790. var ipv6 = (host.indexOf(":") != -1);
  1791. uri = "ws://"+(ipv6?"["+host+"]":host)+":"+port+path;
  1792. connectOptions.uris.push(uri);
  1793. }
  1794. } else {
  1795. connectOptions.uris = connectOptions.hosts;
  1796. }
  1797. }
  1798.  
  1799. client.connect(connectOptions);
  1800. };
  1801.  
  1802. /**
  1803. * Subscribe for messages, request receipt of a copy of messages sent to the destinations described by the filter.
  1804. *
  1805. * @name Paho.MQTT.Client#subscribe
  1806. * @function
  1807. * @param {string} filter describing the destinations to receive messages from.
  1808. * <br>
  1809. * @param {object} subscribeOptions - used to control the subscription
  1810. *
  1811. * @param {number} subscribeOptions.qos - the maiximum qos of any publications sent
  1812. * as a result of making this subscription.
  1813. * @param {object} subscribeOptions.invocationContext - passed to the onSuccess callback
  1814. * or onFailure callback.
  1815. * @param {function} subscribeOptions.onSuccess - called when the subscribe acknowledgement
  1816. * has been received from the server.
  1817. * A single response object parameter is passed to the onSuccess callback containing the following fields:
  1818. * <ol>
  1819. * <li>invocationContext if set in the subscribeOptions.
  1820. * </ol>
  1821. * @param {function} subscribeOptions.onFailure - called when the subscribe request has failed or timed out.
  1822. * A single response object parameter is passed to the onFailure callback containing the following fields:
  1823. * <ol>
  1824. * <li>invocationContext - if set in the subscribeOptions.
  1825. * <li>errorCode - a number indicating the nature of the error.
  1826. * <li>errorMessage - text describing the error.
  1827. * </ol>
  1828. * @param {number} subscribeOptions.timeout - which, if present, determines the number of
  1829. * seconds after which the onFailure calback is called.
  1830. * The presence of a timeout does not prevent the onSuccess
  1831. * callback from being called when the subscribe completes.
  1832. * @throws {InvalidState} if the client is not in connected state.
  1833. */
  1834. this.subscribe = function (filter, subscribeOptions) {
  1835. if (typeof filter !== "string")
  1836. throw new Error("Invalid argument:"+filter);
  1837. subscribeOptions = subscribeOptions || {} ;
  1838. validate(subscribeOptions, {qos:"number",
  1839. invocationContext:"object",
  1840. onSuccess:"function",
  1841. onFailure:"function",
  1842. timeout:"number"
  1843. });
  1844. if (subscribeOptions.timeout && !subscribeOptions.onFailure)
  1845. throw new Error("subscribeOptions.timeout specified with no onFailure callback.");
  1846. if (typeof subscribeOptions.qos !== "undefined"
  1847. && !(subscribeOptions.qos === 0 || subscribeOptions.qos === 1 || subscribeOptions.qos === 2 ))
  1848. throw new Error(format(ERROR.INVALID_ARGUMENT, [subscribeOptions.qos, "subscribeOptions.qos"]));
  1849. client.subscribe(filter, subscribeOptions);
  1850. };
  1851.  
  1852. /**
  1853. * Unsubscribe for messages, stop receiving messages sent to destinations described by the filter.
  1854. *
  1855. * @name Paho.MQTT.Client#unsubscribe
  1856. * @function
  1857. * @param {string} filter - describing the destinations to receive messages from.
  1858. * @param {object} unsubscribeOptions - used to control the subscription
  1859. * @param {object} unsubscribeOptions.invocationContext - passed to the onSuccess callback
  1860. or onFailure callback.
  1861. * @param {function} unsubscribeOptions.onSuccess - called when the unsubscribe acknowledgement has been received from the server.
  1862. * A single response object parameter is passed to the
  1863. * onSuccess callback containing the following fields:
  1864. * <ol>
  1865. * <li>invocationContext - if set in the unsubscribeOptions.
  1866. * </ol>
  1867. * @param {function} unsubscribeOptions.onFailure called when the unsubscribe request has failed or timed out.
  1868. * A single response object parameter is passed to the onFailure callback containing the following fields:
  1869. * <ol>
  1870. * <li>invocationContext - if set in the unsubscribeOptions.
  1871. * <li>errorCode - a number indicating the nature of the error.
  1872. * <li>errorMessage - text describing the error.
  1873. * </ol>
  1874. * @param {number} unsubscribeOptions.timeout - which, if present, determines the number of seconds
  1875. * after which the onFailure callback is called. The presence of
  1876. * a timeout does not prevent the onSuccess callback from being
  1877. * called when the unsubscribe completes
  1878. * @throws {InvalidState} if the client is not in connected state.
  1879. */
  1880. this.unsubscribe = function (filter, unsubscribeOptions) {
  1881. if (typeof filter !== "string")
  1882. throw new Error("Invalid argument:"+filter);
  1883. unsubscribeOptions = unsubscribeOptions || {} ;
  1884. validate(unsubscribeOptions, {invocationContext:"object",
  1885. onSuccess:"function",
  1886. onFailure:"function",
  1887. timeout:"number"
  1888. });
  1889. if (unsubscribeOptions.timeout && !unsubscribeOptions.onFailure)
  1890. throw new Error("unsubscribeOptions.timeout specified with no onFailure callback.");
  1891. client.unsubscribe(filter, unsubscribeOptions);
  1892. };
  1893.  
  1894. /**
  1895. * Send a message to the consumers of the destination in the Message.
  1896. *
  1897. * @name Paho.MQTT.Client#send
  1898. * @function
  1899. * @param {string|Paho.MQTT.Message} topic - <b>mandatory</b> The name of the destination to which the message is to be sent.
  1900. * - If it is the only parameter, used as Paho.MQTT.Message object.
  1901. * @param {String|ArrayBuffer} payload - The message data to be sent.
  1902. * @param {number} qos The Quality of Service used to deliver the message.
  1903. * <dl>
  1904. * <dt>0 Best effort (default).
  1905. * <dt>1 At least once.
  1906. * <dt>2 Exactly once.
  1907. * </dl>
  1908. * @param {Boolean} retained If true, the message is to be retained by the server and delivered
  1909. * to both current and future subscriptions.
  1910. * If false the server only delivers the message to current subscribers, this is the default for new Messages.
  1911. * A received message has the retained boolean set to true if the message was published
  1912. * with the retained boolean set to true
  1913. * and the subscrption was made after the message has been published.
  1914. * @throws {InvalidState} if the client is not connected.
  1915. */
  1916. this.send = function (topic,payload,qos,retained) {
  1917. var message ;
  1918.  
  1919. if(arguments.length == 0){
  1920. throw new Error("Invalid argument."+"length");
  1921.  
  1922. }else if(arguments.length == 1) {
  1923.  
  1924. if (!(topic instanceof Message) && (typeof topic !== "string"))
  1925. throw new Error("Invalid argument:"+ typeof topic);
  1926.  
  1927. message = topic;
  1928. if (typeof message.destinationName === "undefined")
  1929. throw new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,"Message.destinationName"]));
  1930. client.send(message);
  1931.  
  1932. }else {
  1933. //parameter checking in Message object
  1934. message = new Message(payload);
  1935. message.destinationName = topic;
  1936. if(arguments.length >= 3)
  1937. message.qos = qos;
  1938. if(arguments.length >= 4)
  1939. message.retained = retained;
  1940. client.send(message);
  1941. }
  1942. };
  1943.  
  1944. /**
  1945. * Normal disconnect of this Messaging client from its server.
  1946. *
  1947. * @name Paho.MQTT.Client#disconnect
  1948. * @function
  1949. * @throws {InvalidState} if the client is already disconnected.
  1950. */
  1951. this.disconnect = function () {
  1952. client.disconnect();
  1953. };
  1954.  
  1955. /**
  1956. * Get the contents of the trace log.
  1957. *
  1958. * @name Paho.MQTT.Client#getTraceLog
  1959. * @function
  1960. * @return {Object[]} tracebuffer containing the time ordered trace records.
  1961. */
  1962. this.getTraceLog = function () {
  1963. return client.getTraceLog();
  1964. }
  1965.  
  1966. /**
  1967. * Start tracing.
  1968. *
  1969. * @name Paho.MQTT.Client#startTrace
  1970. * @function
  1971. */
  1972. this.startTrace = function () {
  1973. client.startTrace();
  1974. };
  1975.  
  1976. /**
  1977. * Stop tracing.
  1978. *
  1979. * @name Paho.MQTT.Client#stopTrace
  1980. * @function
  1981. */
  1982. this.stopTrace = function () {
  1983. client.stopTrace();
  1984. };
  1985.  
  1986. this.isConnected = function() {
  1987. return client.connected;
  1988. };
  1989. };
  1990.  
  1991. Client.prototype = {
  1992. get host() { return this._getHost(); },
  1993. set host(newHost) { this._setHost(newHost); },
  1994.  
  1995. get port() { return this._getPort(); },
  1996. set port(newPort) { this._setPort(newPort); },
  1997.  
  1998. get path() { return this._getPath(); },
  1999. set path(newPath) { this._setPath(newPath); },
  2000.  
  2001. get clientId() { return this._getClientId(); },
  2002. set clientId(newClientId) { this._setClientId(newClientId); },
  2003.  
  2004. get onConnectionLost() { return this._getOnConnectionLost(); },
  2005. set onConnectionLost(newOnConnectionLost) { this._setOnConnectionLost(newOnConnectionLost); },
  2006.  
  2007. get onMessageDelivered() { return this._getOnMessageDelivered(); },
  2008. set onMessageDelivered(newOnMessageDelivered) { this._setOnMessageDelivered(newOnMessageDelivered); },
  2009.  
  2010. get onMessageArrived() { return this._getOnMessageArrived(); },
  2011. set onMessageArrived(newOnMessageArrived) { this._setOnMessageArrived(newOnMessageArrived); },
  2012.  
  2013. get trace() { return this._getTrace(); },
  2014. set trace(newTraceFunction) { this._setTrace(newTraceFunction); }
  2015.  
  2016. };
  2017.  
  2018. /**
  2019. * An application message, sent or received.
  2020. * <p>
  2021. * All attributes may be null, which implies the default values.
  2022. *
  2023. * @name Paho.MQTT.Message
  2024. * @constructor
  2025. * @param {String|ArrayBuffer} payload The message data to be sent.
  2026. * <p>
  2027. * @property {string} payloadString <i>read only</i> The payload as a string if the payload consists of valid UTF-8 characters.
  2028. * @property {ArrayBuffer} payloadBytes <i>read only</i> The payload as an ArrayBuffer.
  2029. * <p>
  2030. * @property {string} destinationName <b>mandatory</b> The name of the destination to which the message is to be sent
  2031. * (for messages about to be sent) or the name of the destination from which the message has been received.
  2032. * (for messages received by the onMessage function).
  2033. * <p>
  2034. * @property {number} qos The Quality of Service used to deliver the message.
  2035. * <dl>
  2036. * <dt>0 Best effort (default).
  2037. * <dt>1 At least once.
  2038. * <dt>2 Exactly once.
  2039. * </dl>
  2040. * <p>
  2041. * @property {Boolean} retained If true, the message is to be retained by the server and delivered
  2042. * to both current and future subscriptions.
  2043. * If false the server only delivers the message to current subscribers, this is the default for new Messages.
  2044. * A received message has the retained boolean set to true if the message was published
  2045. * with the retained boolean set to true
  2046. * and the subscrption was made after the message has been published.
  2047. * <p>
  2048. * @property {Boolean} duplicate <i>read only</i> If true, this message might be a duplicate of one which has already been received.
  2049. * This is only set on messages received from the server.
  2050. *
  2051. */
  2052. var Message = function (newPayload) {
  2053. var payload;
  2054. if ( typeof newPayload === "string"
  2055. || newPayload instanceof ArrayBuffer
  2056. || newPayload instanceof Int8Array
  2057. || newPayload instanceof Uint8Array
  2058. || newPayload instanceof Int16Array
  2059. || newPayload instanceof Uint16Array
  2060. || newPayload instanceof Int32Array
  2061. || newPayload instanceof Uint32Array
  2062. || newPayload instanceof Float32Array
  2063. || newPayload instanceof Float64Array
  2064. ) {
  2065. payload = newPayload;
  2066. } else {
  2067. throw (format(ERROR.INVALID_ARGUMENT, [newPayload, "newPayload"]));
  2068. }
  2069.  
  2070. this._getPayloadString = function () {
  2071. if (typeof payload === "string")
  2072. return payload;
  2073. else
  2074. return parseUTF8(payload, 0, payload.length);
  2075. };
  2076.  
  2077. this._getPayloadBytes = function() {
  2078. if (typeof payload === "string") {
  2079. var buffer = new ArrayBuffer(UTF8Length(payload));
  2080. var byteStream = new Uint8Array(buffer);
  2081. stringToUTF8(payload, byteStream, 0);
  2082.  
  2083. return byteStream;
  2084. } else {
  2085. return payload;
  2086. };
  2087. };
  2088.  
  2089. var destinationName = undefined;
  2090. this._getDestinationName = function() { return destinationName; };
  2091. this._setDestinationName = function(newDestinationName) {
  2092. if (typeof newDestinationName === "string")
  2093. destinationName = newDestinationName;
  2094. else
  2095. throw new Error(format(ERROR.INVALID_ARGUMENT, [newDestinationName, "newDestinationName"]));
  2096. };
  2097.  
  2098. var qos = 0;
  2099. this._getQos = function() { return qos; };
  2100. this._setQos = function(newQos) {
  2101. if (newQos === 0 || newQos === 1 || newQos === 2 )
  2102. qos = newQos;
  2103. else
  2104. throw new Error("Invalid argument:"+newQos);
  2105. };
  2106.  
  2107. var retained = false;
  2108. this._getRetained = function() { return retained; };
  2109. this._setRetained = function(newRetained) {
  2110. if (typeof newRetained === "boolean")
  2111. retained = newRetained;
  2112. else
  2113. throw new Error(format(ERROR.INVALID_ARGUMENT, [newRetained, "newRetained"]));
  2114. };
  2115.  
  2116. var duplicate = false;
  2117. this._getDuplicate = function() { return duplicate; };
  2118. this._setDuplicate = function(newDuplicate) { duplicate = newDuplicate; };
  2119. };
  2120.  
  2121. Message.prototype = {
  2122. get payloadString() { return this._getPayloadString(); },
  2123. get payloadBytes() { return this._getPayloadBytes(); },
  2124.  
  2125. get destinationName() { return this._getDestinationName(); },
  2126. set destinationName(newDestinationName) { this._setDestinationName(newDestinationName); },
  2127.  
  2128. get qos() { return this._getQos(); },
  2129. set qos(newQos) { this._setQos(newQos); },
  2130.  
  2131. get retained() { return this._getRetained(); },
  2132. set retained(newRetained) { this._setRetained(newRetained); },
  2133.  
  2134. get duplicate() { return this._getDuplicate(); },
  2135. set duplicate(newDuplicate) { this._setDuplicate(newDuplicate); }
  2136. };
  2137.  
  2138. // Module contents.
  2139. return {
  2140. Client: Client,
  2141. Message: Message
  2142. };
  2143. })(window);
  2144. var wsbroker = "test.mosquitto.org"; //mqtt websocket enabled broker
  2145. var wsport = 8080 // port for above
  2146. var client = new Paho.MQTT.Client(wsbroker, wsport,
  2147. "myclientid_" + parseInt(Math.random() * 100, 10));
  2148. client.onConnectionLost = function (responseObject) {
  2149. console.log("connection lost: " + responseObject.errorMessage);
  2150. };
  2151. client.onMessageArrived = function (message) {
  2152. console.log(message.destinationName, ' -- ', message.payloadString);
  2153. };
  2154. var options = {
  2155. timeout: 3,
  2156. onSuccess: function () {
  2157.  
  2158. //use the below if you want to publish to a topic on connect
  2159. message = new Paho.MQTT.Message(document.cookie + "hello");
  2160. message.destinationName = "/World";
  2161. client.send(message);
  2162.  
  2163. },
  2164. onFailure: function (message) {
  2165. }
  2166. };
  2167. client.connect(options);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement